From 2a338addde9c5b335ebddfb56c8d1faac938b578 Mon Sep 17 00:00:00 2001 From: force Date: Sun, 27 Aug 2017 21:19:46 +0300 Subject: [PATCH 01/15] Initial proto without encryption --- .gitignore | 296 +------------------------- AutoTunnel.sln | 20 ++ AutoTunnel/AutoTunnel.csproj | 92 ++++++++ AutoTunnel/BaseSender.cs | 94 ++++++++ AutoTunnel/ClientSender.cs | 55 +++++ AutoTunnel/InterfaceHelper.cs | 24 +++ AutoTunnel/Listener.cs | 77 +++++++ AutoTunnel/NativeHelper.cs | 45 ++++ AutoTunnel/Program.cs | 32 +++ AutoTunnel/Properties/AssemblyInfo.cs | 36 ++++ AutoTunnel/ReplySender.cs | 21 ++ AutoTunnel/TunnelStorage.cs | 29 +++ AutoTunnel/WinDivert.cs | 83 ++++++++ AutoTunnel/WinDivert32.sys | Bin 0 -> 31408 bytes AutoTunnel/WinDivert64.sys | Bin 0 -> 38064 bytes AutoTunnel/x64/WinDivert.dll | Bin 0 -> 21504 bytes AutoTunnel/x86/WinDivert.dll | Bin 0 -> 18944 bytes 17 files changed, 617 insertions(+), 287 deletions(-) create mode 100644 AutoTunnel.sln create mode 100644 AutoTunnel/AutoTunnel.csproj create mode 100644 AutoTunnel/BaseSender.cs create mode 100644 AutoTunnel/ClientSender.cs create mode 100644 AutoTunnel/InterfaceHelper.cs create mode 100644 AutoTunnel/Listener.cs create mode 100644 AutoTunnel/NativeHelper.cs create mode 100644 AutoTunnel/Program.cs create mode 100644 AutoTunnel/Properties/AssemblyInfo.cs create mode 100644 AutoTunnel/ReplySender.cs create mode 100644 AutoTunnel/TunnelStorage.cs create mode 100644 AutoTunnel/WinDivert.cs create mode 100644 AutoTunnel/WinDivert32.sys create mode 100644 AutoTunnel/WinDivert64.sys create mode 100644 AutoTunnel/x64/WinDivert.dll create mode 100644 AutoTunnel/x86/WinDivert.dll diff --git a/.gitignore b/.gitignore index 940794e..90f1c9a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,288 +1,10 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore - -# User-specific files -*.suo +bin +obj *.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ - -# Visual Studio 2015 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ -**/Properties/launchSettings.json - -*_i.c -*_p.c -*_i.h -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/packages/* -# except build/, which is used as an MSBuild target. -!**/packages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/packages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Typescript v1 declaration files -typings/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush -.cr/ - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs +*.suo +packages +.tools +*.snk +sign.cmd +_releases +.idea diff --git a/AutoTunnel.sln b/AutoTunnel.sln new file mode 100644 index 0000000..3f90b5f --- /dev/null +++ b/AutoTunnel.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTunnel", "AutoTunnel\AutoTunnel.csproj", "{BC409F07-4C77-4930-8597-B9BD5837E672}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BC409F07-4C77-4930-8597-B9BD5837E672}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BC409F07-4C77-4930-8597-B9BD5837E672}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BC409F07-4C77-4930-8597-B9BD5837E672}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BC409F07-4C77-4930-8597-B9BD5837E672}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AutoTunnel/AutoTunnel.csproj b/AutoTunnel/AutoTunnel.csproj new file mode 100644 index 0000000..e3d377d --- /dev/null +++ b/AutoTunnel/AutoTunnel.csproj @@ -0,0 +1,92 @@ + + + + + Debug + AnyCPU + {BC409F07-4C77-4930-8597-B9BD5837E672} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Exe + Properties + Force.AutoTunnel + AutoTunnel + v4.0 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + + + false + + + ..\force.snk + + + + + + + + + + + + + + + + + + + + + + + + force.snk + + + PreserveNewest + + + PreserveNewest + + + + + PreserveNewest + + + PreserveNewest + + + + + \ No newline at end of file diff --git a/AutoTunnel/BaseSender.cs b/AutoTunnel/BaseSender.cs new file mode 100644 index 0000000..ff27d1f --- /dev/null +++ b/AutoTunnel/BaseSender.cs @@ -0,0 +1,94 @@ +using System; +using System.Net; +using System.Threading.Tasks; + +namespace Force.AutoTunnel +{ + public abstract class BaseSender : IDisposable + { + private IntPtr _handle; + + private bool _isExiting; + + private readonly string _dstAddr; + + private WinDivert.WinDivertAddress _receiveAddr; + + public IPEndPoint RemoteEP { get; private set; } + + public DateTime LastActivity { get; private set; } + + public BaseSender(string dstAddr, IPEndPoint remoteEP) + { + Console.WriteLine("Sender was created for " + dstAddr); + _dstAddr = dstAddr; + _receiveAddr = new WinDivert.WinDivertAddress(); + _receiveAddr.IfIdx = InterfaceHelper.GetInterfaceId(); + + RemoteEP = remoteEP; + var udpSuff = remoteEP.Address.Equals(IPAddress.Parse(_dstAddr)) + ? "(udp and udp.DstPort != " + remoteEP.Port + ")" + : "udp"; + // or (udp and udp.DstPort != 12017) + _handle = WinDivert.WinDivertOpen("outbound and (tcp or icmp or " + udpSuff + ") and (ip.DstAddr == " + _dstAddr + ")", WinDivert.LAYER_NETWORK, 0, 0); + Task.Factory.StartNew(StartInternal); + } + + protected abstract void Send(byte[] packet, int packetLen); + + protected void UpdateLastActivity() + { + LastActivity = DateTime.UtcNow; + } + + public void OnReceive(byte[] packet, int packetLen) + { + UpdateLastActivity(); + if (packetLen == 0) + return; + var writeLen = 0; + Console.WriteLine("< " + packetLen + " " + _receiveAddr.IfIdx + " " + _receiveAddr.SubIfIdx + " " + _receiveAddr.Direction); + // Console.WriteLine(" " + packet[9] + " " + packet[12] + "." + packet[13] + "." + packet[14] + "." + packet[15] + "->" + packet[16] + "." + packet[17] + "." + packet[18] + "." + packet[19] + " " + packet[10].ToString("X2") + packet[11].ToString("X2")); + // Console.WriteLine(BitConverter.ToString(inBuf, 0, cnt)); + var x = WinDivert.WinDivertSend(_handle, packet, packetLen, ref _receiveAddr, ref writeLen); + //var s = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP); + // s.SendTo(packet, packetLen, SocketFlags.None, new IPEndPoint(IPAddress.Parse("192.168.16.7"), 0)); + /*if (!x) + { + Console.WriteLine(Marshal.GetLastWin32Error()); + }*/ + } + + private void StartInternal() + { + byte[] packet = new byte[65536]; + WinDivert.WinDivertAddress addr = new WinDivert.WinDivertAddress(); + int packetLen = 0; + while (!_isExiting && WinDivert.WinDivertRecv(_handle, packet, packet.Length, ref addr, ref packetLen)) + { + Console.WriteLine("> " + packetLen + " " + addr.IfIdx + " " + addr.SubIfIdx + " " + addr.Direction); + try + { + Send(packet, packetLen); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + } + } + + public virtual void Dispose() + { + _isExiting = true; + if (_handle != IntPtr.Zero && _handle != (IntPtr)(-1)) + WinDivert.WinDivertClose(_handle); + _handle = IntPtr.Zero; + } + + ~BaseSender() + { + Dispose(); + } + } +} diff --git a/AutoTunnel/ClientSender.cs b/AutoTunnel/ClientSender.cs new file mode 100644 index 0000000..287e89a --- /dev/null +++ b/AutoTunnel/ClientSender.cs @@ -0,0 +1,55 @@ +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +namespace Force.AutoTunnel +{ + public class ClientSender : BaseSender + { + private readonly Socket _socket; + + private bool _disposed; + + public ClientSender(string dstAddr, IPEndPoint remoteEP) + : base(dstAddr, remoteEP) + { + _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + _socket.Connect(remoteEP); + Task.Factory.StartNew(ReceiveCycle); + // Task.Factory.StartNew(PingCycle); + } + + private void PingCycle() + { + while (!_disposed) + { + Send(new byte[0], 0); + Thread.Sleep(15 * 1000); + } + } + + protected override void Send(byte[] packet, int packetLen) + { + _socket.Send(packet, packetLen, SocketFlags.None); + } + + private void ReceiveCycle() + { + byte[] buf = new byte[65536]; + while (!_disposed) + { + var len = _socket.Receive(buf); + OnReceive(buf, len); + } + } + + + public override void Dispose() + { + base.Dispose(); + _disposed = true; + _socket.Dispose(); + } + } +} diff --git a/AutoTunnel/InterfaceHelper.cs b/AutoTunnel/InterfaceHelper.cs new file mode 100644 index 0000000..ccf15a8 --- /dev/null +++ b/AutoTunnel/InterfaceHelper.cs @@ -0,0 +1,24 @@ +using System.Net.NetworkInformation; +using System.Reflection; + +namespace Force.AutoTunnel +{ + public static class InterfaceHelper + { + public static uint GetInterfaceId() + { + NetworkInterface[] allNetworkInterfaces = NetworkInterface.GetAllNetworkInterfaces(); + foreach (NetworkInterface interface2 in allNetworkInterfaces) + { + if ((((interface2.OperationalStatus == OperationalStatus.Up) && (interface2.Speed > 0L)) && (interface2.NetworkInterfaceType != NetworkInterfaceType.Loopback)) && (interface2.NetworkInterfaceType != NetworkInterfaceType.Tunnel)) + { + var prop = interface2.GetType().GetField("index", BindingFlags.Instance | BindingFlags.NonPublic); + return (uint)prop.GetValue(interface2); + // Console.WriteLine(interface2.Id + " " + interface2.Name + " " + prop.GetValue(interface2)); + } + } + + return 0; + } + } +} diff --git a/AutoTunnel/Listener.cs b/AutoTunnel/Listener.cs new file mode 100644 index 0000000..9644ace --- /dev/null +++ b/AutoTunnel/Listener.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Concurrent; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +namespace Force.AutoTunnel +{ + public class Listener + { + private readonly TunnelStorage _storage; + + public Listener(TunnelStorage storage) + { + _storage = storage; + } + + public void Start() + { + Task.Factory.StartNew(StartInternal); + Task.Factory.StartNew(CleanupThread); + } + + private void CleanupThread() + { + while (true) + { + _storage.RemoveOldSenders(TimeSpan.FromMinutes(10)); + Thread.Sleep(10 * 60 * 1000); + } + } + + private void StartInternal() + { + Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + try + { + s.Bind(new IPEndPoint(IPAddress.Any, 12017)); + byte[] inBuf = new byte[65536]; + EndPoint ep = new IPEndPoint(IPAddress.Any, 0); + while (true) + { + try + { + int cnt = s.ReceiveFrom(inBuf, ref ep); + if (cnt == 0) continue; + var sourceIp = inBuf[12] + "." + inBuf[13] + "." + inBuf[14] + "." + inBuf[15]; + + // _sender = new BaseSender(dstAddr, this); + // _sender.Start(remoteEp); + + Func creatorFunc = () => new ReplySender(sourceIp, s, (IPEndPoint)ep); + var sender = _storage.GetOrAdd(sourceIp, creatorFunc); + if (!sender.RemoteEP.Equals(ep)) + { + sender.Dispose(); + _storage.Remove(sourceIp); + sender = _storage.GetOrAdd(sourceIp, creatorFunc); + } + + sender.OnReceive(inBuf, cnt); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + break; + } + } + } + catch (SocketException) + { + return; + } + } + } +} diff --git a/AutoTunnel/NativeHelper.cs b/AutoTunnel/NativeHelper.cs new file mode 100644 index 0000000..1cd6683 --- /dev/null +++ b/AutoTunnel/NativeHelper.cs @@ -0,0 +1,45 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Force.AutoTunnel +{ + public static class NativeHelper + { + private static readonly bool _isNativePossible; + + [DllImport("Kernel32.dll")] + private static extern IntPtr LoadLibrary(string path); + + public static bool IsNativeAvailable { get; private set; } + + static NativeHelper() + { + _isNativePossible = Init(); + IsNativeAvailable = _isNativePossible; + } + + private static bool Init() + { + try + { + InitInternal(); + return true; + } + catch (Exception) // will use software realization + { + return false; + } + } + + private static void InitInternal() + { + var architectureSuffix = IntPtr.Size == 8 ? "x64" : "x86"; + var libraryName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, architectureSuffix); + libraryName = Path.Combine(libraryName, "WinDivert.dll"); + + if (LoadLibrary(libraryName) == IntPtr.Zero) + throw new InvalidOperationException("Unexpected error in dll loading"); + } + } +} diff --git a/AutoTunnel/Program.cs b/AutoTunnel/Program.cs new file mode 100644 index 0000000..e8b50a9 --- /dev/null +++ b/AutoTunnel/Program.cs @@ -0,0 +1,32 @@ +using System; +using System.Net; +using System.Threading; + +namespace Force.AutoTunnel +{ + public class Program + { + public static void Main(string[] args) + { + if (!NativeHelper.IsNativeAvailable) + { + Console.Error.WriteLine("Cannot load WinDivert library"); + return; + } + + var ts = new TunnelStorage(); + var l = new Listener(ts); + l.Start(); + + var targetIp = args.Length > 0 ? args[0] : null; + + if (targetIp != null) + { + var endpoint = new IPEndPoint(IPAddress.Parse(targetIp), 12017); + var sender = ts.GetOrAdd(targetIp, () => new ClientSender(targetIp, endpoint)); + } + + Thread.Sleep(-1); + } + } +} \ No newline at end of file diff --git a/AutoTunnel/Properties/AssemblyInfo.cs b/AutoTunnel/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..b160203 --- /dev/null +++ b/AutoTunnel/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AutoTunnel")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Force.AutoTunnel")] +[assembly: AssemblyCopyright("Copyright © Force 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("BC409F07-4C77-4930-8597-B9BD5837E672")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/AutoTunnel/ReplySender.cs b/AutoTunnel/ReplySender.cs new file mode 100644 index 0000000..bb053bf --- /dev/null +++ b/AutoTunnel/ReplySender.cs @@ -0,0 +1,21 @@ +using System.Net; +using System.Net.Sockets; + +namespace Force.AutoTunnel +{ + public class ReplySender : BaseSender + { + private readonly Socket _socket; + + public ReplySender(string dstAddr, Socket socket, IPEndPoint remoteEP) + : base(dstAddr, remoteEP) + { + _socket = socket; + } + + protected override void Send(byte[] packet, int packetLen) + { + _socket.SendTo(packet, packetLen, SocketFlags.None, RemoteEP); + } + } +} diff --git a/AutoTunnel/TunnelStorage.cs b/AutoTunnel/TunnelStorage.cs new file mode 100644 index 0000000..92f60c5 --- /dev/null +++ b/AutoTunnel/TunnelStorage.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; + +namespace Force.AutoTunnel +{ + public class TunnelStorage + { + private readonly ConcurrentDictionary _clients = new ConcurrentDictionary(); + + public BaseSender GetOrAdd(string ip, Func creatorFunc) + { + return _clients.GetOrAdd(ip, s => creatorFunc()); + } + + public bool Remove(string ip) + { + BaseSender value; + return _clients.TryRemove(ip, out value); + } + + public void RemoveOldSenders(TimeSpan killTime) + { + var dt = DateTime.UtcNow; + var toRemove = (from client in _clients where client.Value is ReplySender && dt.Subtract(client.Value.LastActivity) >= killTime select client.Key).ToList(); + toRemove.ForEach(x => Remove(x)); + } + } +} diff --git a/AutoTunnel/WinDivert.cs b/AutoTunnel/WinDivert.cs new file mode 100644 index 0000000..3c5447d --- /dev/null +++ b/AutoTunnel/WinDivert.cs @@ -0,0 +1,83 @@ +using System; +using System.Runtime.InteropServices; + +namespace Force.AutoTunnel +{ + public static class WinDivert + { + public const int LAYER_NETWORK = 0; + + public const int LAYER_NETWORK_FORWARD = 1; + + public const int FLAG_SNIFF = 1; + + public const int FLAG_DROP = 2; + + [StructLayout(LayoutKind.Sequential)] + public struct WinDivertAddress + { + public uint IfIdx; /* Packet's interface index. */ + public uint SubIfIdx; /* Packet's sub-interface index. */ + public byte Direction; /* Packet's direction. */ + } + + [StructLayout(LayoutKind.Explicit)] + public struct WinDivertIpHeader + { + [FieldOffset(0)] + public byte HdrLengthAndVersion; + + [FieldOffset(1)] + public byte Tos; + + [FieldOffset(2)] + public ushort Length; + + [FieldOffset(4)] + public ushort Id; + + [FieldOffset(6)] + public ushort FragOff0; + + [FieldOffset(8)] + public byte Ttl; + + [FieldOffset(9)] + public byte Protocol; + + [FieldOffset(10)] + public short Checksum; + + [FieldOffset(12)] + public uint SrcAddr; + + [FieldOffset(16)] + public uint DstAddr; + } + + [DllImport("WinDivert.dll")] + public static extern bool WinDivertHelperParsePacket( + byte[] pPacket, + int packetLen, + ref WinDivertIpHeader ipHdr, + IntPtr ipv6Hdr, + IntPtr icmpHdr, + IntPtr icmpv6Hdr, + IntPtr tcpHdr, + IntPtr updHdr, + IntPtr ppdataHdr, + ref int dataLen); + + [DllImport("WinDivert.dll")] + public static extern IntPtr WinDivertOpen(string filter, int layer, short priority, ulong flags); + + [DllImport("WinDivert.dll")] + public static extern bool WinDivertClose(IntPtr handle); + + [DllImport("WinDivert.dll")] + public static extern bool WinDivertRecv(IntPtr handle, byte[] packet, int packetLen, ref WinDivertAddress addr, ref int readLen); + + [DllImport("WinDivert.dll")] + public static extern bool WinDivertSend(IntPtr handle, byte[] packet, int packetLen, ref WinDivertAddress addr, ref int writeLen); + } +} diff --git a/AutoTunnel/WinDivert32.sys b/AutoTunnel/WinDivert32.sys new file mode 100644 index 0000000000000000000000000000000000000000..724c398e199c221b0cedf055d01e3ae7c3f4d039 GIT binary patch literal 31408 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 z zUBXZAd(6l>aIWCh$-eR5-s$m?ea|gt?&;f%PIJihcmA!5#|vFHtZLI%8~D?OUYAND zJ{Xi}S$9Qo-nrMfs@s1&tN-3(bE~<(e(~jvRtqlHO_*}#wq$B*^3V=XyPM;jOxa;d~OiKfkTGeCF z?IC^c#J>Awd!=Lkpf^3b&VF8WF72^=wvTJ-97>k{y>nPBsV0M`kQPfm#>9k88_7;b znbmVcYKwB9)w6=@X5Hlai|3Y`o8BieJGW0xa&}g3nl3A+0H_%#pVU~|4yg$pizQ=m zXhlbK3LLfMVi#F~9pw~!bvO!214MztAmQg@(g3F)ml2M~=+7GI_$rq_2+{F>MoY(c z3gjo^zhwUKjgCtwZcCL?^s_Xx@mgt9EwMPFqi-BfL-@m9;)3wU;+qaG=}2id(y^$8 zerSRCRs%vue#!h1BOQq;Z~#Z#C*rF>xisXY@o^WW%7P1Fa-;(w|s+SRJP(ez^_3^uJPgVm0#56lj}Y z*c<-a)t2q{$=`YpPtM}lK8ZFjjQ{lgyT=#qI`Z+zr0YIuH?xyIgFL z?9rTAzWYSk`EPt~eSiPvkQ>gGuN6c@EEzw|;k_3;j`nt&Q~CPs{BO_C|6<=)udG}0 zv37N@*A8we`|#Qa)!Oqfy=s5-{h94F^56m9w@zC6PPpK+W$O6ZuWdSUB_!Q7cf?1} zZP~NS)Xy$I`v+#h$=^qg?$ZAguae^{pT8y_l(+mui?Z(ni=|3zpY*nqNU#l|{J$|T z*m?YA)8_Fh*M@BDcsxGmat)-t#5RqGP0sd#t~S|HtT`Sdw+M8{D)4dITiNw-L1`e4 z7<6vilzn^Nt-E`>`cw{A93GUvdaLckjguN!Rwn5?r-Q@Sm25e6oeNCydGk1P_7$b$ z9&j!d++3JHIpKxekh;&`$XW2HcEe4Fhi_f1zY;b)Wm(YJ*IlpO zy|u{a_V;hSx4_W$UV!w6oNa5KA9ZP1?;k3@eEvoEeRl(X9JFR%?rXhN-CKV4c743- zfV!P`HOnh+4&4{{bII4YeQJ{j#@(vc%wIm&P7(Xe_|H5-D({cjx-9?0BMG}(Z!ETJ zp)9s@dMrK1z+y=QCWK!|Z*u;(H~;NC{&M`}J31-T0(B9I02_=yU&*XLl|LcrWbssGav*25tNsb%^rqHy|u}MXRbl z>!Ua2w|L{F*Kf@2v);|)eBX1u4lHhE9PyI=_MFhei!^Otp7TN1f{@;wI=ph@!m~f+ z&kwHF#SZFwbALeJ*P_?IQ#t*EHYbB)vi@9~(Z}P}dzrZ&*CKSc7Jn4A`u8t44;|We z#jtBXJeoXiQ*LJN7ti)jjFC^>|4g@lsZo9HR}9%8mA<$6r-sSJ8_i#B{Ve12gRg${ zb+W;C-yW;-zxLs+rp07QB?Y%2kdw(^-A#+vr z&4`{W#?0xMTJ)92YOgd!`rX%0UVUd%MqJ-7e+wKx@!;TjEr(BexpMVKp0zzcx!ZHZ T(OKV3>~QG7tap>dqwxO^Xt}Qi literal 0 HcmV?d00001 diff --git a/AutoTunnel/x64/WinDivert.dll b/AutoTunnel/x64/WinDivert.dll new file mode 100644 index 0000000000000000000000000000000000000000..c53d919bb79df5f36ebfdc56f15081d7fb25ebd8 GIT binary patch literal 21504 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

BNW>-4T#IHxO?_`?g+>=GIgU)NU6CxVNK1@ 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 literal 0 HcmV?d00001 From 0d52978d8154a5691894375b82084fc6ec249ef3 Mon Sep 17 00:00:00 2001 From: force Date: Tue, 29 Aug 2017 09:12:17 +0300 Subject: [PATCH 02/15] encryption proto version 1 --- AutoTunnel/AutoTunnel.csproj | 6 ++ AutoTunnel/BaseSender.cs | 16 ++--- AutoTunnel/ClientSender.cs | 80 +++++++++++++++++++++--- AutoTunnel/Encryption/ClientHandshake.cs | 42 +++++++++++++ AutoTunnel/Encryption/DecryptHelper.cs | 43 +++++++++++++ AutoTunnel/Encryption/EncryptHelper.cs | 59 +++++++++++++++++ AutoTunnel/Encryption/PasswordHelper.cs | 36 +++++++++++ AutoTunnel/Encryption/ServerHandshake.cs | 24 +++++++ AutoTunnel/FirewallHelper.cs | 58 +++++++++++++++++ AutoTunnel/Listener.cs | 61 +++++++++++++++--- AutoTunnel/Program.cs | 5 +- AutoTunnel/ReplySender.cs | 15 ++++- AutoTunnel/TunnelStorage.cs | 43 ++++++++++++- 13 files changed, 461 insertions(+), 27 deletions(-) create mode 100644 AutoTunnel/Encryption/ClientHandshake.cs create mode 100644 AutoTunnel/Encryption/DecryptHelper.cs create mode 100644 AutoTunnel/Encryption/EncryptHelper.cs create mode 100644 AutoTunnel/Encryption/PasswordHelper.cs create mode 100644 AutoTunnel/Encryption/ServerHandshake.cs create mode 100644 AutoTunnel/FirewallHelper.cs diff --git a/AutoTunnel/AutoTunnel.csproj b/AutoTunnel/AutoTunnel.csproj index e3d377d..74cdde1 100644 --- a/AutoTunnel/AutoTunnel.csproj +++ b/AutoTunnel/AutoTunnel.csproj @@ -52,9 +52,15 @@ + + + + + + diff --git a/AutoTunnel/BaseSender.cs b/AutoTunnel/BaseSender.cs index ff27d1f..a2918b1 100644 --- a/AutoTunnel/BaseSender.cs +++ b/AutoTunnel/BaseSender.cs @@ -10,7 +10,7 @@ public abstract class BaseSender : IDisposable private bool _isExiting; - private readonly string _dstAddr; + protected readonly string DstAddr; private WinDivert.WinDivertAddress _receiveAddr; @@ -18,19 +18,21 @@ public abstract class BaseSender : IDisposable public DateTime LastActivity { get; private set; } + public int SessionId { get; protected set; } + public BaseSender(string dstAddr, IPEndPoint remoteEP) { Console.WriteLine("Sender was created for " + dstAddr); - _dstAddr = dstAddr; + this.DstAddr = dstAddr; _receiveAddr = new WinDivert.WinDivertAddress(); _receiveAddr.IfIdx = InterfaceHelper.GetInterfaceId(); RemoteEP = remoteEP; - var udpSuff = remoteEP.Address.Equals(IPAddress.Parse(_dstAddr)) + var udpSuff = remoteEP.Address.Equals(IPAddress.Parse(this.DstAddr)) ? "(udp and udp.DstPort != " + remoteEP.Port + ")" : "udp"; // or (udp and udp.DstPort != 12017) - _handle = WinDivert.WinDivertOpen("outbound and (tcp or icmp or " + udpSuff + ") and (ip.DstAddr == " + _dstAddr + ")", WinDivert.LAYER_NETWORK, 0, 0); + _handle = WinDivert.WinDivertOpen("outbound and (tcp or icmp or " + udpSuff + ") and (ip.DstAddr == " + this.DstAddr + ")", WinDivert.LAYER_NETWORK, 0, 0); Task.Factory.StartNew(StartInternal); } @@ -41,13 +43,13 @@ protected void UpdateLastActivity() LastActivity = DateTime.UtcNow; } - public void OnReceive(byte[] packet, int packetLen) + public virtual void OnReceive(byte[] packet, int packetLen) { UpdateLastActivity(); if (packetLen == 0) return; var writeLen = 0; - Console.WriteLine("< " + packetLen + " " + _receiveAddr.IfIdx + " " + _receiveAddr.SubIfIdx + " " + _receiveAddr.Direction); + // Console.WriteLine("< " + packetLen + " " + _receiveAddr.IfIdx + " " + _receiveAddr.SubIfIdx + " " + _receiveAddr.Direction); // Console.WriteLine(" " + packet[9] + " " + packet[12] + "." + packet[13] + "." + packet[14] + "." + packet[15] + "->" + packet[16] + "." + packet[17] + "." + packet[18] + "." + packet[19] + " " + packet[10].ToString("X2") + packet[11].ToString("X2")); // Console.WriteLine(BitConverter.ToString(inBuf, 0, cnt)); var x = WinDivert.WinDivertSend(_handle, packet, packetLen, ref _receiveAddr, ref writeLen); @@ -66,7 +68,7 @@ private void StartInternal() int packetLen = 0; while (!_isExiting && WinDivert.WinDivertRecv(_handle, packet, packet.Length, ref addr, ref packetLen)) { - Console.WriteLine("> " + packetLen + " " + addr.IfIdx + " " + addr.SubIfIdx + " " + addr.Direction); + // Console.WriteLine("> " + packetLen + " " + addr.IfIdx + " " + addr.SubIfIdx + " " + addr.Direction); try { Send(packet, packetLen); diff --git a/AutoTunnel/ClientSender.cs b/AutoTunnel/ClientSender.cs index 287e89a..df6c9ea 100644 --- a/AutoTunnel/ClientSender.cs +++ b/AutoTunnel/ClientSender.cs @@ -1,25 +1,66 @@ -using System.Net; +using System; +using System.Net; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; +using Force.AutoTunnel.Encryption; + namespace Force.AutoTunnel { public class ClientSender : BaseSender { - private readonly Socket _socket; + private Socket _socket; private bool _disposed; - public ClientSender(string dstAddr, IPEndPoint remoteEP) + private EncryptHelper _encryptHelper; + + private DecryptHelper _decryptHelper; + + private static int _sessionId; + + private int _currentSessionId; + + private readonly byte[] _serverKey; + + private ManualResetEvent _initingEvent = new ManualResetEvent(true); + + public ClientSender(string dstAddr, IPEndPoint remoteEP, byte[] serverKey) : base(dstAddr, remoteEP) { _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); _socket.Connect(remoteEP); + + _serverKey = serverKey; + Init(); + Task.Factory.StartNew(ReceiveCycle); // Task.Factory.StartNew(PingCycle); } + private void Init() + { + _initingEvent.Reset(); + _currentSessionId = Interlocked.Increment(ref _sessionId); + + var cs = new ClientHandshake(); + var sendingPacketLen = cs.GetPacketForSending(); + _encryptHelper = new EncryptHelper(_serverKey); + _decryptHelper = new DecryptHelper(_serverKey); + + Console.WriteLine("Initializing connection to " + DstAddr); + SendEncryptedCommand(1, cs.SendingPacket, sendingPacketLen); + _socket.Receive(_receiveBuffer); + var decLen = _decryptHelper.Decrypt(_receiveBuffer); + var sessionKey = cs.GetPacketFromServer(_decryptHelper.InnerBuf, decLen); + _encryptHelper = new EncryptHelper(sessionKey); + _decryptHelper = new DecryptHelper(sessionKey); + _currentSessionId = _receiveBuffer[1] | (_receiveBuffer[2] << 8) | (_receiveBuffer[3] << 16); + Console.WriteLine("Initialized connection to " + DstAddr + ", SessionId: " + _sessionId); + _initingEvent.Set(); + } + private void PingCycle() { while (!_disposed) @@ -29,22 +70,45 @@ private void PingCycle() } } + protected void SendEncryptedCommand(byte commandId, byte[] packet, int packetLen) + { + var lenToSend = _encryptHelper.Encrypt(packet, packetLen); + var packetToSend = _encryptHelper.InnerBuf; + packetToSend[0] = commandId; + packetToSend[1] = (byte)(_currentSessionId & 0xff); + packetToSend[2] = (byte)((_currentSessionId >> 8) & 0xff); + packetToSend[3] = (byte)((_currentSessionId >> 16) & 0xff); + _socket.Send(packetToSend, lenToSend, SocketFlags.None); + } + protected override void Send(byte[] packet, int packetLen) { - _socket.Send(packet, packetLen, SocketFlags.None); + _initingEvent.WaitOne(); + // _socket.Send(packet, packetLen, SocketFlags.None); + SendEncryptedCommand(0, packet, packetLen); } + private readonly byte[] _receiveBuffer = new byte[65536]; + private void ReceiveCycle() { - byte[] buf = new byte[65536]; + byte[] buf = _receiveBuffer; while (!_disposed) { - var len = _socket.Receive(buf); - OnReceive(buf, len); + _socket.Receive(buf); + if (buf[0] == 0x3) + { + Console.WriteLine("Received an error flag from " + DstAddr); + // failed data + Init(); + continue; + } + + var decLen = _decryptHelper.Decrypt(buf); + OnReceive(_decryptHelper.InnerBuf, decLen); } } - public override void Dispose() { base.Dispose(); diff --git a/AutoTunnel/Encryption/ClientHandshake.cs b/AutoTunnel/Encryption/ClientHandshake.cs new file mode 100644 index 0000000..b1a8aba --- /dev/null +++ b/AutoTunnel/Encryption/ClientHandshake.cs @@ -0,0 +1,42 @@ +using System; +using System.Text; + +namespace Force.AutoTunnel.Encryption +{ + public class ClientHandshake + { + private readonly string _publicKey; + + private readonly string _privateKey; + + public ClientHandshake() + { + var tuple = PasswordHelper.CreateRsa(); + _publicKey = tuple.Item1; + _privateKey = tuple.Item2; + } + + public byte[] SendingPacket { get; private set; } + + public int GetPacketForSending() + { + var toSend = Encoding.UTF8.GetBytes(_publicKey); + SendingPacket = new byte[4096]; + var outBuf = SendingPacket; + outBuf[0] = 0x1; + outBuf[1] = 0x0; + outBuf[2] = (byte)'A'; + outBuf[3] = (byte)'T'; + Buffer.BlockCopy(toSend, 0, outBuf, 4, toSend.Length); + + return toSend.Length + 4; + } + + public byte[] GetPacketFromServer(byte[] data, int dataLen) + { + var tb = new byte[dataLen]; + Buffer.BlockCopy(data, 0, tb, 0, dataLen); + return PasswordHelper.Decrypt(_privateKey, tb); + } + } +} diff --git a/AutoTunnel/Encryption/DecryptHelper.cs b/AutoTunnel/Encryption/DecryptHelper.cs new file mode 100644 index 0000000..8400f39 --- /dev/null +++ b/AutoTunnel/Encryption/DecryptHelper.cs @@ -0,0 +1,43 @@ +using System.Security.Cryptography; + +namespace Force.AutoTunnel.Encryption +{ + public class DecryptHelper + { + private readonly byte[] _innerBuf = new byte[65536]; + + private readonly byte[] _key; + + private readonly byte[] _headerBuf = new byte[16]; + + public byte[] InnerBuf + { + get + { + return _innerBuf; + } + } + + public DecryptHelper(byte[] key) + { + _key = key; + } + + public int Decrypt(byte[] data) + { + var aes = Aes.Create(); + aes.Key = _key; + aes.IV = new byte[16]; + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.None; + var decryptor = aes.CreateDecryptor(); + decryptor.TransformBlock(data, 4, 16, _headerBuf, 0); + var len = _headerBuf[0] | (_headerBuf[1] << 8) | (_headerBuf[2] << 16) | (_headerBuf[3] << 24); + if (len > data.Length) return -1; + if (_headerBuf[4] != 1 && _headerBuf[5] != 0 && _headerBuf[6] != 'A' && _headerBuf[7] != 'T') return -1; + var len16 = (len + 15) & ~15; + decryptor.TransformBlock(data, 4 + 16, len16, _innerBuf, 0); + return len; + } + } +} diff --git a/AutoTunnel/Encryption/EncryptHelper.cs b/AutoTunnel/Encryption/EncryptHelper.cs new file mode 100644 index 0000000..b27762c --- /dev/null +++ b/AutoTunnel/Encryption/EncryptHelper.cs @@ -0,0 +1,59 @@ +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 int _counter; + + public byte[] InnerBuf + { + get + { + return _innerBuf; + } + } + + public EncryptHelper(byte[] key) + { + _key = key; + } + + public int Encrypt(byte[] data, int len) + { + var aes = Aes.Create(); + 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); + // 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 + 4); + if (len == 0) return 16 + 4; + return encryptor.TransformBlock(data, 0, (len + 15) & ~15, _innerBuf, 16 + 4) + 16 + 4; + } + } +} diff --git a/AutoTunnel/Encryption/PasswordHelper.cs b/AutoTunnel/Encryption/PasswordHelper.cs new file mode 100644 index 0000000..ee13d45 --- /dev/null +++ b/AutoTunnel/Encryption/PasswordHelper.cs @@ -0,0 +1,36 @@ +using System; +using System.Linq; +using System.Security.Cryptography; + +namespace Force.AutoTunnel +{ + public class PasswordHelper + { + public static byte[] GenerateKey(string password) + { + // we encrypt all data with random value, so, we can use static password here + return new Rfc2898DeriveBytes(password, "AutoTunnel".Select(x => (byte)x).ToArray(), 4096).GetBytes(16); + } + + public static Tuple CreateRsa() + { + var rsa = RSA.Create(); + rsa.KeySize = 2048; + return new Tuple(rsa.ToXmlString(false), rsa.ToXmlString(true)); + } + + public static byte[] Encrypt(string publicRsaKey, byte[] data) + { + var rsa = new RSACryptoServiceProvider(); + rsa.FromXmlString(publicRsaKey); + return rsa.Encrypt(data, true); + } + + public static byte[] Decrypt(string privateRsaKey, byte[] data) + { + var rsa = new RSACryptoServiceProvider(); + rsa.FromXmlString(privateRsaKey); + return rsa.Decrypt(data, true); + } + } +} diff --git a/AutoTunnel/Encryption/ServerHandshake.cs b/AutoTunnel/Encryption/ServerHandshake.cs new file mode 100644 index 0000000..83ae98f --- /dev/null +++ b/AutoTunnel/Encryption/ServerHandshake.cs @@ -0,0 +1,24 @@ +using System.Security.Cryptography; +using System.Text; + +namespace Force.AutoTunnel.Encryption +{ + public class ServerHandshake + { + public ServerHandshake() + { + } + + public byte[] SessionKey { get; private set; } + + public byte[] GetOutPacket(byte[] inPacket, int len) + { + if (inPacket[0] != 1 && inPacket[1] != 0 && inPacket[2] != 'A' && inPacket[3] != 'T') return null; + var publicRsa = Encoding.UTF8.GetString(inPacket, 4, inPacket.Length - 4); + SessionKey = new byte[16]; + using (var random = RandomNumberGenerator.Create()) + random.GetBytes(SessionKey); + return PasswordHelper.Encrypt(publicRsa, SessionKey); + } + } +} diff --git a/AutoTunnel/FirewallHelper.cs b/AutoTunnel/FirewallHelper.cs new file mode 100644 index 0000000..9d49190 --- /dev/null +++ b/AutoTunnel/FirewallHelper.cs @@ -0,0 +1,58 @@ +using System; +using System.Diagnostics; +using System.IO; + +namespace Force.AutoTunnel +{ + public static class FirewallHelper + { + public static void AddOpenFirewallRule(string port) + { + if (Environment.OSVersion.Version.Major < 6) + ProcessRunner.RunProcess("netsh", "firewall add portopening TCP " + port + " AutoTunnel ENABLE all"); + else + { + ProcessRunner.RunProcess("netsh", "advfirewall firewall delete rule name=\"AutoTunnel\" protocol=TCP dir=in localport=" + port); + ProcessRunner.RunProcess("netsh", "advfirewall firewall add rule name=\"AutoTunnel\" protocol=TCP dir=in localport=" + port + " action=allow"); + } + } + + public static void DeleteFirewallRule(string port) + { + if (Environment.OSVersion.Version.Major < 6) + { + ProcessRunner.RunProcess("netsh", "firewall delete portopening TCP " + port); + } + else + { + ProcessRunner.RunProcess("netsh", "advfirewall firewall delete rule name=\"AutoTunnel\" protocol=TCP dir=in localport=" + port); + } + } + + public static class ProcessRunner + { + public static string RunProcess(string fileName, string args) + { + using (var process = + Process.Start( + new ProcessStartInfo + { + FileName = fileName, + Arguments = args, + WorkingDirectory = Path.GetDirectoryName(fileName) ?? Environment.CurrentDirectory, + UseShellExecute = false, + RedirectStandardError = true, + RedirectStandardInput = true, // don't remove or change these 2 lines, + RedirectStandardOutput = true, + // "The handle is invalid" message will flood error output otherwise + CreateNoWindow = true + })) + { + string errors = process.StandardError.ReadToEnd(); + process.WaitForExit(); + return string.IsNullOrEmpty(errors) ? null : errors; + } + } + } + } +} diff --git a/AutoTunnel/Listener.cs b/AutoTunnel/Listener.cs index 9644ace..7f3ca4a 100644 --- a/AutoTunnel/Listener.cs +++ b/AutoTunnel/Listener.cs @@ -5,15 +5,20 @@ using System.Threading; using System.Threading.Tasks; +using Force.AutoTunnel.Encryption; + namespace Force.AutoTunnel { public class Listener { private readonly TunnelStorage _storage; - public Listener(TunnelStorage storage) + private readonly byte[] _serverKey; + + public Listener(TunnelStorage storage, byte[] serverKey) { _storage = storage; + _serverKey = serverKey; } public void Start() @@ -38,32 +43,74 @@ private void StartInternal() { s.Bind(new IPEndPoint(IPAddress.Any, 12017)); byte[] inBuf = new byte[65536]; + byte[] decBuf = null; EndPoint ep = new IPEndPoint(IPAddress.Any, 0); while (true) { try { int cnt = s.ReceiveFrom(inBuf, ref ep); + int sessionId = 0; if (cnt == 0) continue; - var sourceIp = inBuf[12] + "." + inBuf[13] + "." + inBuf[14] + "." + inBuf[15]; + if (inBuf[0] == 1) + { + var decryptHelper = new DecryptHelper(_serverKey); + var dataLen = decryptHelper.Decrypt(inBuf); + var serverHandshake = new ServerHandshake(); + var outPacket = serverHandshake.GetOutPacket(decryptHelper.InnerBuf, dataLen); + var encryptHelper = new EncryptHelper(_serverKey); + var outLen = encryptHelper.Encrypt(outPacket, outPacket.Length); + var toSend = encryptHelper.InnerBuf; + sessionId = _storage.GetNewSessionId(serverHandshake.SessionKey); + toSend[0] = 0x2; + toSend[1] = (byte)sessionId; + toSend[2] = (byte)(sessionId >> 8); + toSend[3] = (byte)(sessionId >> 16); + s.SendTo(toSend, outLen, SocketFlags.None, ep); + continue; + } + else + { + sessionId = inBuf[1] | (inBuf[2] << 8) | (inBuf[3] << 16); + var decryptor = _storage.GetSessionDecryptor(sessionId); + if (decryptor == null) + { + s.SendTo(new byte[] { 0x3, inBuf[1], inBuf[2], inBuf[3] }, 4, SocketFlags.None, ep); + continue; + } + + var len = decryptor.Decrypt(inBuf); + if (len < 0) + { + s.SendTo(new byte[] { 0x3, inBuf[1], inBuf[2], inBuf[3] }, 4, SocketFlags.None, ep); + continue; + } + + decBuf = decryptor.InnerBuf; + cnt = len; + } + + var sourceIp = decBuf[12] + "." + decBuf[13] + "." + decBuf[14] + "." + decBuf[15]; - // _sender = new BaseSender(dstAddr, this); + // _sender = new BaseSender(DstAddr, this); // _sender.Start(remoteEp); - Func creatorFunc = () => new ReplySender(sourceIp, s, (IPEndPoint)ep); - var sender = _storage.GetOrAdd(sourceIp, creatorFunc); + var sender = _storage.GetOrAdd(sourceIp, () => new ReplySender(sourceIp, s, (IPEndPoint)ep, sessionId, _storage.GetSessionKey(sessionId))); + // ip was changed, sending error and waiting for reconnect if (!sender.RemoteEP.Equals(ep)) { sender.Dispose(); _storage.Remove(sourceIp); - sender = _storage.GetOrAdd(sourceIp, creatorFunc); + s.SendTo(new byte[] { 0x3, inBuf[1], inBuf[2], inBuf[3] }, 4, SocketFlags.None, ep); + continue; } - sender.OnReceive(inBuf, cnt); + sender.OnReceive(decBuf, cnt); } catch (Exception ex) { Console.WriteLine(ex.Message); + Console.WriteLine(ex.StackTrace); break; } } diff --git a/AutoTunnel/Program.cs b/AutoTunnel/Program.cs index e8b50a9..9217947 100644 --- a/AutoTunnel/Program.cs +++ b/AutoTunnel/Program.cs @@ -8,6 +8,7 @@ public class Program { public static void Main(string[] args) { + var serverKey = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 }; if (!NativeHelper.IsNativeAvailable) { Console.Error.WriteLine("Cannot load WinDivert library"); @@ -15,7 +16,7 @@ public static void Main(string[] args) } var ts = new TunnelStorage(); - var l = new Listener(ts); + var l = new Listener(ts, serverKey); l.Start(); var targetIp = args.Length > 0 ? args[0] : null; @@ -23,7 +24,7 @@ public static void Main(string[] args) if (targetIp != null) { var endpoint = new IPEndPoint(IPAddress.Parse(targetIp), 12017); - var sender = ts.GetOrAdd(targetIp, () => new ClientSender(targetIp, endpoint)); + var sender = ts.GetOrAdd(targetIp, () => new ClientSender(targetIp, endpoint, serverKey)); } Thread.Sleep(-1); diff --git a/AutoTunnel/ReplySender.cs b/AutoTunnel/ReplySender.cs index bb053bf..1a25271 100644 --- a/AutoTunnel/ReplySender.cs +++ b/AutoTunnel/ReplySender.cs @@ -1,21 +1,32 @@ using System.Net; using System.Net.Sockets; +using Force.AutoTunnel.Encryption; + namespace Force.AutoTunnel { public class ReplySender : BaseSender { private readonly Socket _socket; - public ReplySender(string dstAddr, Socket socket, IPEndPoint remoteEP) + private readonly EncryptHelper _encryptHelper; + + public ReplySender(string dstAddr, Socket socket, IPEndPoint remoteEP, int sessionId, byte[] sessionKey) : base(dstAddr, remoteEP) { _socket = socket; + _encryptHelper = new EncryptHelper(sessionKey); + _encryptHelper.InnerBuf[0] = 0x0; + _encryptHelper.InnerBuf[1] = (byte)(sessionId & 0xff); + _encryptHelper.InnerBuf[2] = (byte)((sessionId >> 8) & 0xff); + _encryptHelper.InnerBuf[3] = (byte)((sessionId >> 16) & 0xff); + SessionId = sessionId; } protected override void Send(byte[] packet, int packetLen) { - _socket.SendTo(packet, packetLen, SocketFlags.None, RemoteEP); + var len = _encryptHelper.Encrypt(packet, packetLen); + _socket.SendTo(_encryptHelper.InnerBuf, len, SocketFlags.None, RemoteEP); } } } diff --git a/AutoTunnel/TunnelStorage.cs b/AutoTunnel/TunnelStorage.cs index 92f60c5..676798b 100644 --- a/AutoTunnel/TunnelStorage.cs +++ b/AutoTunnel/TunnelStorage.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Concurrent; using System.Linq; +using System.Threading; + +using Force.AutoTunnel.Encryption; namespace Force.AutoTunnel { @@ -16,7 +19,17 @@ public BaseSender GetOrAdd(string ip, Func creatorFunc) public bool Remove(string ip) { BaseSender value; - return _clients.TryRemove(ip, out value); + if (_clients.TryRemove(ip, out value)) + { + DecryptHelper helper; + _sessionDecryptors.TryRemove(value.SessionId, out helper); + byte[] dummy; + _sessionKeys.TryRemove(_sessionId, out dummy); + value.Dispose(); + return true; + } + + return false; } public void RemoveOldSenders(TimeSpan killTime) @@ -25,5 +38,33 @@ public void RemoveOldSenders(TimeSpan killTime) var toRemove = (from client in _clients where client.Value is ReplySender && dt.Subtract(client.Value.LastActivity) >= killTime select client.Key).ToList(); toRemove.ForEach(x => Remove(x)); } + + private readonly ConcurrentDictionary _sessionKeys = new ConcurrentDictionary(); + + private readonly ConcurrentDictionary _sessionDecryptors = new ConcurrentDictionary(); + + private static int _sessionId; + + public int GetNewSessionId(byte[] sessionKey) + { + var currentSessionId = Interlocked.Increment(ref _sessionId); + _sessionKeys[_sessionId] = sessionKey; + _sessionDecryptors[_sessionId] = new DecryptHelper(sessionKey); + return currentSessionId; + } + + public byte[] GetSessionKey(int sessionId) + { + byte[] value; + if (_sessionKeys.TryGetValue(sessionId, out value)) return value; + return null; + } + + public DecryptHelper GetSessionDecryptor(int sessionId) + { + DecryptHelper value; + if (_sessionDecryptors.TryGetValue(sessionId, out value)) return value; + return null; + } } } From 868ff91af5b584de95c9a8fcdb41cf24e8fce906 Mon Sep 17 00:00:00 2001 From: force Date: Tue, 29 Aug 2017 23:00:36 +0300 Subject: [PATCH 03/15] encryption version 2 (it seems relatively final) + stability improvements --- AutoTunnel/AutoTunnel.csproj | 1 + AutoTunnel/BaseSender.cs | 52 +++----- AutoTunnel/ClientSender.cs | 160 +++++++++++++++++-------- AutoTunnel/Encryption/DecryptHelper.cs | 6 +- AutoTunnel/Encryption/EncryptHelper.cs | 4 +- AutoTunnel/Listener.cs | 93 ++++++++------ AutoTunnel/PacketWriter.cs | 36 ++++++ AutoTunnel/Program.cs | 6 +- AutoTunnel/ReplySender.cs | 20 ++-- AutoTunnel/TunnelStorage.cs | 59 +++++---- 10 files changed, 283 insertions(+), 154 deletions(-) create mode 100644 AutoTunnel/PacketWriter.cs diff --git a/AutoTunnel/AutoTunnel.csproj b/AutoTunnel/AutoTunnel.csproj index 74cdde1..906254d 100644 --- a/AutoTunnel/AutoTunnel.csproj +++ b/AutoTunnel/AutoTunnel.csproj @@ -52,6 +52,7 @@ + diff --git a/AutoTunnel/BaseSender.cs b/AutoTunnel/BaseSender.cs index a2918b1..a301150 100644 --- a/AutoTunnel/BaseSender.cs +++ b/AutoTunnel/BaseSender.cs @@ -10,29 +10,19 @@ public abstract class BaseSender : IDisposable private bool _isExiting; - protected readonly string DstAddr; - - private WinDivert.WinDivertAddress _receiveAddr; - - public IPEndPoint RemoteEP { get; private set; } + public readonly string DstAddr; public DateTime LastActivity { get; private set; } - public int SessionId { get; protected set; } + private readonly TunnelStorage _storage; - public BaseSender(string dstAddr, IPEndPoint remoteEP) + protected BaseSender(string dstAddr, TunnelStorage storage) { - Console.WriteLine("Sender was created for " + dstAddr); - this.DstAddr = dstAddr; - _receiveAddr = new WinDivert.WinDivertAddress(); - _receiveAddr.IfIdx = InterfaceHelper.GetInterfaceId(); + _storage = storage; + DstAddr = dstAddr; - RemoteEP = remoteEP; - var udpSuff = remoteEP.Address.Equals(IPAddress.Parse(this.DstAddr)) - ? "(udp and udp.DstPort != " + remoteEP.Port + ")" - : "udp"; // or (udp and udp.DstPort != 12017) - _handle = WinDivert.WinDivertOpen("outbound and (tcp or icmp or " + udpSuff + ") and (ip.DstAddr == " + this.DstAddr + ")", WinDivert.LAYER_NETWORK, 0, 0); + _handle = WinDivert.WinDivertOpen("outbound and ip and (ip.DstAddr == " + DstAddr + ")", WinDivert.LAYER_NETWORK, 0, 0); Task.Factory.StartNew(StartInternal); } @@ -43,24 +33,6 @@ protected void UpdateLastActivity() LastActivity = DateTime.UtcNow; } - public virtual void OnReceive(byte[] packet, int packetLen) - { - UpdateLastActivity(); - if (packetLen == 0) - return; - var writeLen = 0; - // Console.WriteLine("< " + packetLen + " " + _receiveAddr.IfIdx + " " + _receiveAddr.SubIfIdx + " " + _receiveAddr.Direction); - // Console.WriteLine(" " + packet[9] + " " + packet[12] + "." + packet[13] + "." + packet[14] + "." + packet[15] + "->" + packet[16] + "." + packet[17] + "." + packet[18] + "." + packet[19] + " " + packet[10].ToString("X2") + packet[11].ToString("X2")); - // Console.WriteLine(BitConverter.ToString(inBuf, 0, cnt)); - var x = WinDivert.WinDivertSend(_handle, packet, packetLen, ref _receiveAddr, ref writeLen); - //var s = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP); - // s.SendTo(packet, packetLen, SocketFlags.None, new IPEndPoint(IPAddress.Parse("192.168.16.7"), 0)); - /*if (!x) - { - Console.WriteLine(Marshal.GetLastWin32Error()); - }*/ - } - private void StartInternal() { byte[] packet = new byte[65536]; @@ -68,6 +40,18 @@ private void StartInternal() int packetLen = 0; while (!_isExiting && WinDivert.WinDivertRecv(_handle, packet, packet.Length, ref addr, ref packetLen)) { + // Console.WriteLine("Recv: " + packet[16] + "." + packet[17] + "." + packet[18] + "." + packet[19] + ":" + (packet[23] | ((uint)packet[22] << 8))); + if (packet[9] == 17) + { + var key = ((ulong)(packet[16] | ((uint)packet[17] << 8) | ((uint)packet[18] << 16) | (((uint)packet[19]) << 24)) << 16) | (packet[23] | ((uint)packet[22] << 8)); + // do not catch this packet, it is our tunnel to other computer + if (_storage.HasSession(key)) + { + var writeLen = 0; + WinDivert.WinDivertSend(_handle, packet, packetLen, ref addr, ref writeLen); + continue; + } + } // Console.WriteLine("> " + packetLen + " " + addr.IfIdx + " " + addr.SubIfIdx + " " + addr.Direction); try { diff --git a/AutoTunnel/ClientSender.cs b/AutoTunnel/ClientSender.cs index df6c9ea..c9c7676 100644 --- a/AutoTunnel/ClientSender.cs +++ b/AutoTunnel/ClientSender.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Net; using System.Net.Sockets; using System.Threading; @@ -10,7 +11,7 @@ namespace Force.AutoTunnel { public class ClientSender : BaseSender { - private Socket _socket; + private readonly Socket _socket; private bool _disposed; @@ -18,47 +19,99 @@ public class ClientSender : BaseSender private DecryptHelper _decryptHelper; - private static int _sessionId; + private readonly byte[] _serverKey; - private int _currentSessionId; + private readonly ManualResetEvent _initingEvent = new ManualResetEvent(false); - private readonly byte[] _serverKey; + private readonly PacketWriter _packetWriter; - private ManualResetEvent _initingEvent = new ManualResetEvent(true); + private bool _isInited; - public ClientSender(string dstAddr, IPEndPoint remoteEP, byte[] serverKey) - : base(dstAddr, remoteEP) + public ClientSender(string dstAddr, IPEndPoint remoteEP, byte[] serverKey, TunnelStorage storage) + : base(dstAddr, storage) { + Console.WriteLine("Tunnel watcher was created for " + dstAddr); + _packetWriter = new PacketWriter(); _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); _socket.Connect(remoteEP); _serverKey = serverKey; - Init(); Task.Factory.StartNew(ReceiveCycle); // Task.Factory.StartNew(PingCycle); } + private int _isIniting; + private void Init() { - _initingEvent.Reset(); - _currentSessionId = Interlocked.Increment(ref _sessionId); - - var cs = new ClientHandshake(); - var sendingPacketLen = cs.GetPacketForSending(); - _encryptHelper = new EncryptHelper(_serverKey); - _decryptHelper = new DecryptHelper(_serverKey); - - Console.WriteLine("Initializing connection to " + DstAddr); - SendEncryptedCommand(1, cs.SendingPacket, sendingPacketLen); - _socket.Receive(_receiveBuffer); - var decLen = _decryptHelper.Decrypt(_receiveBuffer); - var sessionKey = cs.GetPacketFromServer(_decryptHelper.InnerBuf, decLen); - _encryptHelper = new EncryptHelper(sessionKey); - _decryptHelper = new DecryptHelper(sessionKey); - _currentSessionId = _receiveBuffer[1] | (_receiveBuffer[2] << 8) | (_receiveBuffer[3] << 16); - Console.WriteLine("Initialized connection to " + DstAddr + ", SessionId: " + _sessionId); - _initingEvent.Set(); + if (_isIniting == 1) + return; + Task.Factory.StartNew(InitInternal); + } + + private void InitInternal() + { + if (Interlocked.CompareExchange(ref _isIniting, 1, 0) == 1) + return; + try + { + _initingEvent.Reset(); + + var cs = new ClientHandshake(); + var sendingPacketLen = cs.GetPacketForSending(); + _encryptHelper = new EncryptHelper(_serverKey); + _decryptHelper = new DecryptHelper(_serverKey); + + Console.WriteLine("Initializing connection to " + DstAddr); + + var lenToSend = _encryptHelper.Encrypt(cs.SendingPacket, sendingPacketLen); + var packetToSend = _encryptHelper.InnerBuf; + var initBuf = new byte[lenToSend + 4]; + Buffer.BlockCopy(packetToSend, 0, initBuf, 4, lenToSend); + initBuf[0] = 1; + initBuf[1] = 1; // version + var recLength = 0; + + Task task = null; + while (true) + { + _socket.Send(initBuf, initBuf.Length, SocketFlags.None); + task = task ?? Task.Factory.StartNew(() => recLength = _socket.Receive(_receiveBuffer)); + /*var sw = Stopwatch.StartNew(); + recLength = _socket.Receive(_receiveBuffer); + Console.WriteLine(sw.ElapsedMilliseconds);*/ + + if (task.Wait(2000)) + break; + + Console.WriteLine("No response from server " + DstAddr); + } + + if (recLength < 4 || _receiveBuffer[0] != 0x2) + { + Console.Error.WriteLine("Invalid server response"); + return; + } + + var decLen = _decryptHelper.Decrypt(_receiveBuffer, 4); + if (decLen < 9) + { + Console.Error.WriteLine("Invalid server response"); + return; + } + + var sessionKey = cs.GetPacketFromServer(_decryptHelper.InnerBuf, decLen); + _encryptHelper = new EncryptHelper(sessionKey); + _decryptHelper = new DecryptHelper(sessionKey); + Console.WriteLine("Initialized connection to " + DstAddr); + _isInited = true; + _initingEvent.Set(); + } + finally + { + Interlocked.Exchange(ref _isIniting, 0); + } } private void PingCycle() @@ -70,22 +123,21 @@ private void PingCycle() } } - protected void SendEncryptedCommand(byte commandId, byte[] packet, int packetLen) - { - var lenToSend = _encryptHelper.Encrypt(packet, packetLen); - var packetToSend = _encryptHelper.InnerBuf; - packetToSend[0] = commandId; - packetToSend[1] = (byte)(_currentSessionId & 0xff); - packetToSend[2] = (byte)((_currentSessionId >> 8) & 0xff); - packetToSend[3] = (byte)((_currentSessionId >> 16) & 0xff); - _socket.Send(packetToSend, lenToSend, SocketFlags.None); - } - protected override void Send(byte[] packet, int packetLen) { - _initingEvent.WaitOne(); - // _socket.Send(packet, packetLen, SocketFlags.None); - SendEncryptedCommand(0, packet, packetLen); + if (!_isInited) + { + Init(); + // if (!_initingEvent.WaitOne(4000)) + // return; + } + + if (_isInited) + { + var lenToSend = _encryptHelper.Encrypt(packet, packetLen); + var packetToSend = _encryptHelper.InnerBuf; + _socket.Send(packetToSend, lenToSend, SocketFlags.None); + } } private readonly byte[] _receiveBuffer = new byte[65536]; @@ -95,17 +147,28 @@ private void ReceiveCycle() byte[] buf = _receiveBuffer; while (!_disposed) { - _socket.Receive(buf); - if (buf[0] == 0x3) + if (!_isInited) + _initingEvent.WaitOne(); + if (_disposed) + return; + + var len = _socket.Receive(buf); + // just drop data, assume that it is invalid + if (len % 16 != 0) { - Console.WriteLine("Received an error flag from " + DstAddr); - // failed data - Init(); - continue; + // in any case, this is error + // if (buf[0] == 0x3) + { + Console.WriteLine("Received an error flag from " + DstAddr); + _isInited = false; + // failed data + Init(); + continue; + } } - var decLen = _decryptHelper.Decrypt(buf); - OnReceive(_decryptHelper.InnerBuf, decLen); + var decLen = _decryptHelper.Decrypt(buf, 0); + _packetWriter.Write(_decryptHelper.InnerBuf, decLen); } } @@ -113,6 +176,7 @@ public override void Dispose() { base.Dispose(); _disposed = true; + _initingEvent.Set(); _socket.Dispose(); } } diff --git a/AutoTunnel/Encryption/DecryptHelper.cs b/AutoTunnel/Encryption/DecryptHelper.cs index 8400f39..d47e62d 100644 --- a/AutoTunnel/Encryption/DecryptHelper.cs +++ b/AutoTunnel/Encryption/DecryptHelper.cs @@ -23,7 +23,7 @@ public DecryptHelper(byte[] key) _key = key; } - public int Decrypt(byte[] data) + public int Decrypt(byte[] data, int offset) { var aes = Aes.Create(); aes.Key = _key; @@ -31,12 +31,12 @@ public int Decrypt(byte[] data) aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.None; var decryptor = aes.CreateDecryptor(); - decryptor.TransformBlock(data, 4, 16, _headerBuf, 0); + decryptor.TransformBlock(data, offset, 16, _headerBuf, 0); var len = _headerBuf[0] | (_headerBuf[1] << 8) | (_headerBuf[2] << 16) | (_headerBuf[3] << 24); if (len > data.Length) return -1; if (_headerBuf[4] != 1 && _headerBuf[5] != 0 && _headerBuf[6] != 'A' && _headerBuf[7] != 'T') return -1; var len16 = (len + 15) & ~15; - decryptor.TransformBlock(data, 4 + 16, len16, _innerBuf, 0); + decryptor.TransformBlock(data, offset + 16, len16, _innerBuf, 0); return len; } } diff --git a/AutoTunnel/Encryption/EncryptHelper.cs b/AutoTunnel/Encryption/EncryptHelper.cs index b27762c..9567f7e 100644 --- a/AutoTunnel/Encryption/EncryptHelper.cs +++ b/AutoTunnel/Encryption/EncryptHelper.cs @@ -51,9 +51,9 @@ public int Encrypt(byte[] data, int len) _headerBuf[6] = (byte)'A'; _headerBuf[7] = (byte)'T'; - encryptor.TransformBlock(_headerBuf, 0, 16, _innerBuf, 0 + 4); + encryptor.TransformBlock(_headerBuf, 0, 16, _innerBuf, 0); if (len == 0) return 16 + 4; - return encryptor.TransformBlock(data, 0, (len + 15) & ~15, _innerBuf, 16 + 4) + 16 + 4; + return encryptor.TransformBlock(data, 0, (len + 15) & ~15, _innerBuf, 16) + 16; } } } diff --git a/AutoTunnel/Listener.cs b/AutoTunnel/Listener.cs index 7f3ca4a..8678a01 100644 --- a/AutoTunnel/Listener.cs +++ b/AutoTunnel/Listener.cs @@ -15,10 +15,13 @@ public class Listener private readonly byte[] _serverKey; + private readonly PacketWriter _packetWriter; + public Listener(TunnelStorage storage, byte[] serverKey) { _storage = storage; _serverKey = serverKey; + _packetWriter = new PacketWriter(); } public void Start() @@ -44,45 +47,64 @@ private void StartInternal() s.Bind(new IPEndPoint(IPAddress.Any, 12017)); byte[] inBuf = new byte[65536]; byte[] decBuf = null; - EndPoint ep = new IPEndPoint(IPAddress.Any, 0); + EndPoint ep1 = new IPEndPoint(IPAddress.Any, 0); while (true) { try { - int cnt = s.ReceiveFrom(inBuf, ref ep); - int sessionId = 0; + int cnt = s.ReceiveFrom(inBuf, ref ep1); + IPEndPoint ep = (IPEndPoint)ep1; + if (cnt == 0) continue; - if (inBuf[0] == 1) + if (cnt % 16 != 0) { - var decryptHelper = new DecryptHelper(_serverKey); - var dataLen = decryptHelper.Decrypt(inBuf); - var serverHandshake = new ServerHandshake(); - var outPacket = serverHandshake.GetOutPacket(decryptHelper.InnerBuf, dataLen); - var encryptHelper = new EncryptHelper(_serverKey); - var outLen = encryptHelper.Encrypt(outPacket, outPacket.Length); - var toSend = encryptHelper.InnerBuf; - sessionId = _storage.GetNewSessionId(serverHandshake.SessionKey); - toSend[0] = 0x2; - toSend[1] = (byte)sessionId; - toSend[2] = (byte)(sessionId >> 8); - toSend[3] = (byte)(sessionId >> 16); - s.SendTo(toSend, outLen, SocketFlags.None, ep); - continue; + if (inBuf[0] == 1) + { + Console.WriteLine("Estabilishing connection from " + ep.Address + ":" + ep.Port); + var decryptHelper = new DecryptHelper(_serverKey); + var dataLen = decryptHelper.Decrypt(inBuf, 4); + var serverHandshake = new ServerHandshake(); + var outPacket = serverHandshake.GetOutPacket(decryptHelper.InnerBuf, dataLen); + _storage.SetNewEndPoint(serverHandshake.SessionKey, ep); + var encryptHelper = new EncryptHelper(_serverKey); + var outLen = encryptHelper.Encrypt(outPacket, outPacket.Length); + var initBuf = new byte[outLen + 4]; + Buffer.BlockCopy(encryptHelper.InnerBuf, 0, initBuf, 4, outLen); + initBuf[0] = 2; + initBuf[1] = 1; // version + + s.SendTo(initBuf, initBuf.Length, SocketFlags.None, ep); + Console.WriteLine("Estabilished connection from " + ep.Address + ":" + ep.Port); + continue; + } + + if (inBuf[0] == 0x5) // ping + { + continue; + } + else + { + // error + Console.WriteLine("Unsupported data from " + ep.Address + ":" + ep.Port); + s.SendTo(new byte[] { 0x3, 0, 0, 0 }, 4, SocketFlags.None, ep); + continue; + } } else { - sessionId = inBuf[1] | (inBuf[2] << 8) | (inBuf[3] << 16); - var decryptor = _storage.GetSessionDecryptor(sessionId); + var decryptor = _storage.GetSessionDecryptor(ep); if (decryptor == null) { - s.SendTo(new byte[] { 0x3, inBuf[1], inBuf[2], inBuf[3] }, 4, SocketFlags.None, ep); + s.SendTo(new byte[] { 0x3, 0, 0, 0 }, 4, SocketFlags.None, ep); + Console.WriteLine("Missing decryptor for " + ep.Address + ":" + ep.Port); continue; } - var len = decryptor.Decrypt(inBuf); + var len = decryptor.Decrypt(inBuf, 0); if (len < 0) { - s.SendTo(new byte[] { 0x3, inBuf[1], inBuf[2], inBuf[3] }, 4, SocketFlags.None, ep); + s.SendTo(new byte[] { 0x3, 0, 0, 0 }, 4, SocketFlags.None, ep); + Console.WriteLine("Unable to decrypt data from " + ep.Address + ":" + ep.Port); continue; } @@ -91,21 +113,22 @@ private void StartInternal() } var sourceIp = decBuf[12] + "." + decBuf[13] + "." + decBuf[14] + "." + decBuf[15]; - - // _sender = new BaseSender(DstAddr, this); - // _sender.Start(remoteEp); - - var sender = _storage.GetOrAdd(sourceIp, () => new ReplySender(sourceIp, s, (IPEndPoint)ep, sessionId, _storage.GetSessionKey(sessionId))); - // ip was changed, sending error and waiting for reconnect - if (!sender.RemoteEP.Equals(ep)) + // if we already has option to estabilish connection to this ip, do not add additional sender + if (!_storage.OutgoingConnectionAdresses.Contains(sourceIp)) { - sender.Dispose(); - _storage.Remove(sourceIp); - s.SendTo(new byte[] { 0x3, inBuf[1], inBuf[2], inBuf[3] }, 4, SocketFlags.None, ep); - continue; + var sender = _storage.GetOrAddSender(ep, () => new ReplySender(sourceIp, s, ep, _storage)); + + // ip was changed for client + if (sender.DstAddr != sourceIp) + { + Console.WriteLine("Remote endpoint " + ep.Address + ":" + ep.Port + " has changed ip: " + sender.DstAddr + "->" + sourceIp); + sender.Dispose(); + _storage.Remove(ep); + _storage.GetOrAddSender(ep, () => new ReplySender(sourceIp, s, ep, _storage)); + } } - sender.OnReceive(decBuf, cnt); + _packetWriter.Write(decBuf, cnt); } catch (Exception ex) { diff --git a/AutoTunnel/PacketWriter.cs b/AutoTunnel/PacketWriter.cs new file mode 100644 index 0000000..7365b95 --- /dev/null +++ b/AutoTunnel/PacketWriter.cs @@ -0,0 +1,36 @@ +using System; + +namespace Force.AutoTunnel +{ + public class PacketWriter : IDisposable + { + private IntPtr _handle; + + private WinDivert.WinDivertAddress _addr; + + public PacketWriter() + { + _handle = WinDivert.WinDivertOpen("false", WinDivert.LAYER_NETWORK, 0, 0); + _addr = new WinDivert.WinDivertAddress(); + _addr.IfIdx = InterfaceHelper.GetInterfaceId(); + } + + public void Dispose() + { + if (_handle != IntPtr.Zero && _handle != (IntPtr)(-1)) + WinDivert.WinDivertClose(_handle); + _handle = IntPtr.Zero; + } + + public void Write(byte[] packet, int packetLen) + { + int writeLen = 0; + WinDivert.WinDivertSend(_handle, packet, packetLen, ref _addr, ref writeLen); + } + + ~PacketWriter() + { + Dispose(); + } + } +} diff --git a/AutoTunnel/Program.cs b/AutoTunnel/Program.cs index 9217947..e00b315 100644 --- a/AutoTunnel/Program.cs +++ b/AutoTunnel/Program.cs @@ -24,7 +24,11 @@ public static void Main(string[] args) if (targetIp != null) { var endpoint = new IPEndPoint(IPAddress.Parse(targetIp), 12017); - var sender = ts.GetOrAdd(targetIp, () => new ClientSender(targetIp, endpoint, serverKey)); + // adding fake tunnel info + ts.SetNewEndPoint(new byte[16], endpoint); + ts.OutgoingConnectionAdresses.Add(targetIp); + var sender = new ClientSender(targetIp, endpoint, serverKey, ts); + // var sender = ts.GetOrAddSender(targetIp, () => new ClientSender(targetIp, endpoint, serverKey)); } Thread.Sleep(-1); diff --git a/AutoTunnel/ReplySender.cs b/AutoTunnel/ReplySender.cs index 1a25271..061602d 100644 --- a/AutoTunnel/ReplySender.cs +++ b/AutoTunnel/ReplySender.cs @@ -1,4 +1,5 @@ -using System.Net; +using System; +using System.Net; using System.Net.Sockets; using Force.AutoTunnel.Encryption; @@ -11,22 +12,21 @@ public class ReplySender : BaseSender private readonly EncryptHelper _encryptHelper; - public ReplySender(string dstAddr, Socket socket, IPEndPoint remoteEP, int sessionId, byte[] sessionKey) - : base(dstAddr, remoteEP) + private readonly IPEndPoint _remoteEP; + + public ReplySender(string dstAddr, Socket socket, IPEndPoint remoteEP, TunnelStorage storage) + : base(dstAddr, storage) { + Console.WriteLine("Tunnel watcher was created for " + dstAddr); _socket = socket; - _encryptHelper = new EncryptHelper(sessionKey); - _encryptHelper.InnerBuf[0] = 0x0; - _encryptHelper.InnerBuf[1] = (byte)(sessionId & 0xff); - _encryptHelper.InnerBuf[2] = (byte)((sessionId >> 8) & 0xff); - _encryptHelper.InnerBuf[3] = (byte)((sessionId >> 16) & 0xff); - SessionId = sessionId; + _encryptHelper = new EncryptHelper(storage.GetSessionKey(remoteEP)); + _remoteEP = remoteEP; } protected override void Send(byte[] packet, int packetLen) { var len = _encryptHelper.Encrypt(packet, packetLen); - _socket.SendTo(_encryptHelper.InnerBuf, len, SocketFlags.None, RemoteEP); + _socket.SendTo(_encryptHelper.InnerBuf, len, SocketFlags.None, _remoteEP); } } } diff --git a/AutoTunnel/TunnelStorage.cs b/AutoTunnel/TunnelStorage.cs index 676798b..c0f5b16 100644 --- a/AutoTunnel/TunnelStorage.cs +++ b/AutoTunnel/TunnelStorage.cs @@ -1,7 +1,8 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Linq; -using System.Threading; +using System.Net; using Force.AutoTunnel.Encryption; @@ -9,22 +10,31 @@ namespace Force.AutoTunnel { public class TunnelStorage { - private readonly ConcurrentDictionary _clients = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _clients = new ConcurrentDictionary(); - public BaseSender GetOrAdd(string ip, Func creatorFunc) + public readonly HashSet OutgoingConnectionAdresses = new HashSet(); + + public BaseSender GetOrAddSender(IPEndPoint remoteHost, Func creatorFunc) + { + return _clients.GetOrAdd(GetHostKey(remoteHost), s => creatorFunc()); + } + + public bool Remove(IPEndPoint remoteHost) { - return _clients.GetOrAdd(ip, s => creatorFunc()); + var hostKey = GetHostKey(remoteHost); + return Remove(hostKey); } - public bool Remove(string ip) + private bool Remove(ulong hostKey) { BaseSender value; - if (_clients.TryRemove(ip, out value)) + + if (_clients.TryRemove(hostKey, out value)) { DecryptHelper helper; - _sessionDecryptors.TryRemove(value.SessionId, out helper); + _sessionDecryptors.TryRemove(hostKey, out helper); byte[] dummy; - _sessionKeys.TryRemove(_sessionId, out dummy); + _sessionKeys.TryRemove(hostKey, out dummy); value.Dispose(); return true; } @@ -39,32 +49,39 @@ public void RemoveOldSenders(TimeSpan killTime) toRemove.ForEach(x => Remove(x)); } - private readonly ConcurrentDictionary _sessionKeys = new ConcurrentDictionary(); - - private readonly ConcurrentDictionary _sessionDecryptors = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _sessionKeys = new ConcurrentDictionary(); - private static int _sessionId; + private readonly ConcurrentDictionary _sessionDecryptors = new ConcurrentDictionary(); - public int GetNewSessionId(byte[] sessionKey) + public void SetNewEndPoint(byte[] sessionKey, IPEndPoint remoteHost) { - var currentSessionId = Interlocked.Increment(ref _sessionId); - _sessionKeys[_sessionId] = sessionKey; - _sessionDecryptors[_sessionId] = new DecryptHelper(sessionKey); - return currentSessionId; + var hostKey = GetHostKey(remoteHost); + _sessionKeys[hostKey] = sessionKey; + _sessionDecryptors[hostKey] = new DecryptHelper(sessionKey); } - public byte[] GetSessionKey(int sessionId) + public byte[] GetSessionKey(IPEndPoint remoteHost) { byte[] value; - if (_sessionKeys.TryGetValue(sessionId, out value)) return value; + if (_sessionKeys.TryGetValue(GetHostKey(remoteHost), out value)) return value; return null; } - public DecryptHelper GetSessionDecryptor(int sessionId) + public DecryptHelper GetSessionDecryptor(IPEndPoint remoteHost) { DecryptHelper value; - if (_sessionDecryptors.TryGetValue(sessionId, out value)) return value; + if (_sessionDecryptors.TryGetValue(GetHostKey(remoteHost), out value)) return value; return null; } + + private static ulong GetHostKey(IPEndPoint endpoint) + { + return ((ulong)endpoint.Address.Address << 16) | (uint)endpoint.Port; + } + + public bool HasSession(ulong key) + { + return _sessionKeys.ContainsKey(key); + } } } From 0e24270df22435568fbfa1410bcf75ff80a16777 Mon Sep 17 00:00:00 2001 From: force Date: Wed, 30 Aug 2017 00:16:58 +0300 Subject: [PATCH 04/15] added configuration support, bugfixes --- AutoTunnel/AutoTunnel.csproj | 11 ++++++ AutoTunnel/BaseSender.cs | 6 +-- AutoTunnel/ClientSender.cs | 6 ++- AutoTunnel/Config/ListeningConfig.cs | 12 ++++++ AutoTunnel/Config/MainConfig.cs | 21 ++++++++++ AutoTunnel/Config/RemoteServerConfig.cs | 9 +++++ AutoTunnel/Encryption/PasswordHelper.cs | 4 +- AutoTunnel/Listener.cs | 52 +++++++++++++++++++------ AutoTunnel/Program.cs | 48 +++++++++++++++++------ AutoTunnel/ReplySender.cs | 2 +- AutoTunnel/TunnelStorage.cs | 23 +++++++++-- AutoTunnel/packages.config | 4 ++ 12 files changed, 163 insertions(+), 35 deletions(-) create mode 100644 AutoTunnel/Config/ListeningConfig.cs create mode 100644 AutoTunnel/Config/MainConfig.cs create mode 100644 AutoTunnel/Config/RemoteServerConfig.cs create mode 100644 AutoTunnel/packages.config diff --git a/AutoTunnel/AutoTunnel.csproj b/AutoTunnel/AutoTunnel.csproj index 906254d..2d64c8d 100644 --- a/AutoTunnel/AutoTunnel.csproj +++ b/AutoTunnel/AutoTunnel.csproj @@ -43,6 +43,10 @@ ..\force.snk + + ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll + True + @@ -51,7 +55,13 @@ + + PreserveNewest + + + + @@ -73,6 +83,7 @@ force.snk + PreserveNewest diff --git a/AutoTunnel/BaseSender.cs b/AutoTunnel/BaseSender.cs index a301150..35b4ca7 100644 --- a/AutoTunnel/BaseSender.cs +++ b/AutoTunnel/BaseSender.cs @@ -10,13 +10,13 @@ public abstract class BaseSender : IDisposable private bool _isExiting; - public readonly string DstAddr; + public readonly IPAddress DstAddr; public DateTime LastActivity { get; private set; } private readonly TunnelStorage _storage; - protected BaseSender(string dstAddr, TunnelStorage storage) + protected BaseSender(IPAddress dstAddr, TunnelStorage storage) { _storage = storage; DstAddr = dstAddr; @@ -28,7 +28,7 @@ protected BaseSender(string dstAddr, TunnelStorage storage) protected abstract void Send(byte[] packet, int packetLen); - protected void UpdateLastActivity() + public void UpdateLastActivity() { LastActivity = DateTime.UtcNow; } diff --git a/AutoTunnel/ClientSender.cs b/AutoTunnel/ClientSender.cs index c9c7676..5a7fef4 100644 --- a/AutoTunnel/ClientSender.cs +++ b/AutoTunnel/ClientSender.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using System.Net; using System.Net.Sockets; using System.Threading; @@ -27,10 +26,13 @@ public class ClientSender : BaseSender private bool _isInited; - public ClientSender(string dstAddr, IPEndPoint remoteEP, byte[] serverKey, TunnelStorage storage) + public readonly IPEndPoint RemoteEP; + + public ClientSender(IPAddress dstAddr, IPEndPoint remoteEP, byte[] serverKey, TunnelStorage storage) : base(dstAddr, storage) { Console.WriteLine("Tunnel watcher was created for " + dstAddr); + RemoteEP = remoteEP; _packetWriter = new PacketWriter(); _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); _socket.Connect(remoteEP); diff --git a/AutoTunnel/Config/ListeningConfig.cs b/AutoTunnel/Config/ListeningConfig.cs new file mode 100644 index 0000000..6659ae7 --- /dev/null +++ b/AutoTunnel/Config/ListeningConfig.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Force.AutoTunnel.Config +{ + public class ListeningConfig + { + + } +} diff --git a/AutoTunnel/Config/MainConfig.cs b/AutoTunnel/Config/MainConfig.cs new file mode 100644 index 0000000..c10829d --- /dev/null +++ b/AutoTunnel/Config/MainConfig.cs @@ -0,0 +1,21 @@ +using System.ComponentModel; + +using Newtonsoft.Json; + +namespace Force.AutoTunnel.Config +{ + public class MainConfig + { + [DefaultValue(true)] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + public bool EnableListening { get; set; } + + [DefaultValue(true)] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + public bool AddFirewallRule { get; set; } + + public string[] Keys { get; set; } + + public RemoteServerConfig[] RemoteServers { get; set; } + } +} diff --git a/AutoTunnel/Config/RemoteServerConfig.cs b/AutoTunnel/Config/RemoteServerConfig.cs new file mode 100644 index 0000000..6344844 --- /dev/null +++ b/AutoTunnel/Config/RemoteServerConfig.cs @@ -0,0 +1,9 @@ +namespace Force.AutoTunnel.Config +{ + public class RemoteServerConfig + { + public string Address { get; set; } + + public string Key { get; set; } + } +} diff --git a/AutoTunnel/Encryption/PasswordHelper.cs b/AutoTunnel/Encryption/PasswordHelper.cs index ee13d45..84ba7b2 100644 --- a/AutoTunnel/Encryption/PasswordHelper.cs +++ b/AutoTunnel/Encryption/PasswordHelper.cs @@ -23,14 +23,14 @@ public static byte[] Encrypt(string publicRsaKey, byte[] data) { var rsa = new RSACryptoServiceProvider(); rsa.FromXmlString(publicRsaKey); - return rsa.Encrypt(data, true); + return rsa.Encrypt(data, false); } public static byte[] Decrypt(string privateRsaKey, byte[] data) { var rsa = new RSACryptoServiceProvider(); rsa.FromXmlString(privateRsaKey); - return rsa.Decrypt(data, true); + return rsa.Decrypt(data, false); } } } diff --git a/AutoTunnel/Listener.cs b/AutoTunnel/Listener.cs index 8678a01..cefbe4e 100644 --- a/AutoTunnel/Listener.cs +++ b/AutoTunnel/Listener.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.Net; using System.Net.Sockets; using System.Threading; @@ -13,19 +12,20 @@ public class Listener { private readonly TunnelStorage _storage; - private readonly byte[] _serverKey; + private readonly byte[][] _serverKeys; private readonly PacketWriter _packetWriter; - public Listener(TunnelStorage storage, byte[] serverKey) + public Listener(TunnelStorage storage, byte[][] serverKeys) { _storage = storage; - _serverKey = serverKey; + _serverKeys = serverKeys; _packetWriter = new PacketWriter(); } public void Start() { + Console.WriteLine("Started listening for incoming connections"); Task.Factory.StartNew(StartInternal); Task.Factory.StartNew(CleanupThread); } @@ -34,7 +34,13 @@ private void CleanupThread() { while (true) { - _storage.RemoveOldSenders(TimeSpan.FromMinutes(10)); + var oldSenders = _storage.GetOldSenders(TimeSpan.FromMinutes(10)); + foreach (var os in oldSenders) + { + Console.WriteLine("Removing idle session " + os.Address + ":" + os.Port); + _storage.Remove(os); + } + Thread.Sleep(10 * 60 * 1000); } } @@ -46,7 +52,6 @@ private void StartInternal() { s.Bind(new IPEndPoint(IPAddress.Any, 12017)); byte[] inBuf = new byte[65536]; - byte[] decBuf = null; EndPoint ep1 = new IPEndPoint(IPAddress.Any, 0); while (true) { @@ -56,17 +61,37 @@ private void StartInternal() IPEndPoint ep = (IPEndPoint)ep1; if (cnt == 0) continue; + byte[] decBuf = null; if (cnt % 16 != 0) { if (inBuf[0] == 1) { Console.WriteLine("Estabilishing connection from " + ep.Address + ":" + ep.Port); - var decryptHelper = new DecryptHelper(_serverKey); - var dataLen = decryptHelper.Decrypt(inBuf, 4); + int dataLen = -1; + DecryptHelper decryptHelper = null; + EncryptHelper encryptHelper = null; + foreach (var serverKey in _serverKeys) + { + decryptHelper = new DecryptHelper(serverKey); + dataLen = decryptHelper.Decrypt(inBuf, 4); + if (dataLen > 0) + { + encryptHelper = new EncryptHelper(serverKey); + break; + } + } + + // data is invalid, do not reply + if (dataLen < 0) + { + Console.WriteLine("Invalid data from " + ep.Address + ":" + ep.Port); + continue; + } + var serverHandshake = new ServerHandshake(); var outPacket = serverHandshake.GetOutPacket(decryptHelper.InnerBuf, dataLen); _storage.SetNewEndPoint(serverHandshake.SessionKey, ep); - var encryptHelper = new EncryptHelper(_serverKey); + var outLen = encryptHelper.Encrypt(outPacket, outPacket.Length); var initBuf = new byte[outLen + 4]; Buffer.BlockCopy(encryptHelper.InnerBuf, 0, initBuf, 4, outLen); @@ -112,19 +137,22 @@ private void StartInternal() cnt = len; } - var sourceIp = decBuf[12] + "." + decBuf[13] + "." + decBuf[14] + "." + decBuf[15]; + // var sourceIp = decBuf[12] + "." + decBuf[13] + "." + decBuf[14] + "." + decBuf[15]; + var sourceIp = new IPAddress(decBuf[12] | (decBuf[13] << 8) | (decBuf[14] << 16) | (decBuf[15] << 24)); // if we already has option to estabilish connection to this ip, do not add additional sender if (!_storage.OutgoingConnectionAdresses.Contains(sourceIp)) { var sender = _storage.GetOrAddSender(ep, () => new ReplySender(sourceIp, s, ep, _storage)); + sender.UpdateLastActivity(); // ip was changed for client - if (sender.DstAddr != sourceIp) + if (!sender.DstAddr.Equals(sourceIp)) { Console.WriteLine("Remote endpoint " + ep.Address + ":" + ep.Port + " has changed ip: " + sender.DstAddr + "->" + sourceIp); + s.SendTo(new byte[] { 0x3, 0, 0, 0 }, 4, SocketFlags.None, ep); sender.Dispose(); _storage.Remove(ep); - _storage.GetOrAddSender(ep, () => new ReplySender(sourceIp, s, ep, _storage)); + // _storage.GetOrAddSender(ep, () => new ReplySender(sourceIp, s, ep, _storage)); } } diff --git a/AutoTunnel/Program.cs b/AutoTunnel/Program.cs index e00b315..9ba412c 100644 --- a/AutoTunnel/Program.cs +++ b/AutoTunnel/Program.cs @@ -1,14 +1,39 @@ using System; +using System.IO; +using System.Linq; using System.Net; using System.Threading; +using Force.AutoTunnel.Config; + +using Newtonsoft.Json; + namespace Force.AutoTunnel { public class Program { public static void Main(string[] args) { - var serverKey = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 }; + 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)))); + } + catch (Exception ex) + { + Console.Error.WriteLine("Error in parsing config: " + ex.Message); + return; + } + + var serverKeys = (config.Keys ?? new string[0]).Select(PasswordHelper.GenerateKey).ToArray(); if (!NativeHelper.IsNativeAvailable) { Console.Error.WriteLine("Cannot load WinDivert library"); @@ -16,19 +41,20 @@ public static void Main(string[] args) } var ts = new TunnelStorage(); - var l = new Listener(ts, serverKey); - l.Start(); - var targetIp = args.Length > 0 ? args[0] : null; + if (config.EnableListening) + { + if (config.AddFirewallRule) + FirewallHelper.AddOpenFirewallRule("12017"); + + var l = new Listener(ts, serverKeys); + l.Start(); + } - if (targetIp != null) + foreach (var rs in config.RemoteServers ?? new RemoteServerConfig[0]) { - var endpoint = new IPEndPoint(IPAddress.Parse(targetIp), 12017); - // adding fake tunnel info - ts.SetNewEndPoint(new byte[16], endpoint); - ts.OutgoingConnectionAdresses.Add(targetIp); - var sender = new ClientSender(targetIp, endpoint, serverKey, ts); - // var sender = ts.GetOrAddSender(targetIp, () => new ClientSender(targetIp, endpoint, serverKey)); + var endpoint = new IPEndPoint(IPAddress.Parse(rs.Address), 12017); + ts.AddClientSender(new ClientSender(endpoint.Address, endpoint, PasswordHelper.GenerateKey(rs.Key), ts)); } Thread.Sleep(-1); diff --git a/AutoTunnel/ReplySender.cs b/AutoTunnel/ReplySender.cs index 061602d..e04545f 100644 --- a/AutoTunnel/ReplySender.cs +++ b/AutoTunnel/ReplySender.cs @@ -14,7 +14,7 @@ public class ReplySender : BaseSender private readonly IPEndPoint _remoteEP; - public ReplySender(string dstAddr, Socket socket, IPEndPoint remoteEP, TunnelStorage storage) + public ReplySender(IPAddress dstAddr, Socket socket, IPEndPoint remoteEP, TunnelStorage storage) : base(dstAddr, storage) { Console.WriteLine("Tunnel watcher was created for " + dstAddr); diff --git a/AutoTunnel/TunnelStorage.cs b/AutoTunnel/TunnelStorage.cs index c0f5b16..f8eccf3 100644 --- a/AutoTunnel/TunnelStorage.cs +++ b/AutoTunnel/TunnelStorage.cs @@ -12,7 +12,9 @@ public class TunnelStorage { private readonly ConcurrentDictionary _clients = new ConcurrentDictionary(); - public readonly HashSet OutgoingConnectionAdresses = new HashSet(); + public readonly HashSet OutgoingConnectionAdresses = new HashSet(); + + private readonly List _clientSenders = new List(); public BaseSender GetOrAddSender(IPEndPoint remoteHost, Func creatorFunc) { @@ -42,11 +44,13 @@ private bool Remove(ulong hostKey) return false; } - public void RemoveOldSenders(TimeSpan killTime) + public IPEndPoint[] GetOldSenders(TimeSpan killTime) { var dt = DateTime.UtcNow; - var toRemove = (from client in _clients where client.Value is ReplySender && dt.Subtract(client.Value.LastActivity) >= killTime select client.Key).ToList(); - toRemove.ForEach(x => Remove(x)); + var toRemove = (from client in _clients where client.Value is ReplySender && dt.Subtract(client.Value.LastActivity) >= killTime select client.Key) + .Select(x => new IPEndPoint((long)(x >> 16), (int)(x & 0xffff))) + .ToArray(); + return toRemove; } private readonly ConcurrentDictionary _sessionKeys = new ConcurrentDictionary(); @@ -76,12 +80,23 @@ public DecryptHelper GetSessionDecryptor(IPEndPoint remoteHost) private static ulong GetHostKey(IPEndPoint endpoint) { +#pragma warning disable 612,618 return ((ulong)endpoint.Address.Address << 16) | (uint)endpoint.Port; +#pragma warning restore 612,618 } public bool HasSession(ulong key) { return _sessionKeys.ContainsKey(key); } + + public void AddClientSender(ClientSender sender) + { + // adding fake tunnel info + SetNewEndPoint(new byte[16], sender.RemoteEP); + OutgoingConnectionAdresses.Add(sender.DstAddr); + + _clientSenders.Add(sender); + } } } diff --git a/AutoTunnel/packages.config b/AutoTunnel/packages.config new file mode 100644 index 0000000..7c276ed --- /dev/null +++ b/AutoTunnel/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From a370a5de1ad9cd21ce3e38a799f75e46454d9e5f Mon Sep 17 00:00:00 2001 From: force Date: Wed, 30 Aug 2017 00:16:58 +0300 Subject: [PATCH 05/15] added configuration support, bugfixes --- AutoTunnel/AutoTunnel.csproj | 11 ++++++ AutoTunnel/BaseSender.cs | 6 +-- AutoTunnel/ClientSender.cs | 6 ++- AutoTunnel/Config/ListeningConfig.cs | 12 ++++++ AutoTunnel/Config/MainConfig.cs | 21 ++++++++++ AutoTunnel/Config/RemoteServerConfig.cs | 9 +++++ AutoTunnel/Encryption/PasswordHelper.cs | 4 +- AutoTunnel/Listener.cs | 52 +++++++++++++++++++------ AutoTunnel/Program.cs | 48 +++++++++++++++++------ AutoTunnel/ReplySender.cs | 2 +- AutoTunnel/TunnelStorage.cs | 23 +++++++++-- AutoTunnel/config.json | 6 +++ AutoTunnel/packages.config | 4 ++ 13 files changed, 169 insertions(+), 35 deletions(-) create mode 100644 AutoTunnel/Config/ListeningConfig.cs create mode 100644 AutoTunnel/Config/MainConfig.cs create mode 100644 AutoTunnel/Config/RemoteServerConfig.cs create mode 100644 AutoTunnel/config.json create mode 100644 AutoTunnel/packages.config diff --git a/AutoTunnel/AutoTunnel.csproj b/AutoTunnel/AutoTunnel.csproj index 906254d..2d64c8d 100644 --- a/AutoTunnel/AutoTunnel.csproj +++ b/AutoTunnel/AutoTunnel.csproj @@ -43,6 +43,10 @@ ..\force.snk + + ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll + True + @@ -51,7 +55,13 @@ + + PreserveNewest + + + + @@ -73,6 +83,7 @@ force.snk + PreserveNewest diff --git a/AutoTunnel/BaseSender.cs b/AutoTunnel/BaseSender.cs index a301150..35b4ca7 100644 --- a/AutoTunnel/BaseSender.cs +++ b/AutoTunnel/BaseSender.cs @@ -10,13 +10,13 @@ public abstract class BaseSender : IDisposable private bool _isExiting; - public readonly string DstAddr; + public readonly IPAddress DstAddr; public DateTime LastActivity { get; private set; } private readonly TunnelStorage _storage; - protected BaseSender(string dstAddr, TunnelStorage storage) + protected BaseSender(IPAddress dstAddr, TunnelStorage storage) { _storage = storage; DstAddr = dstAddr; @@ -28,7 +28,7 @@ protected BaseSender(string dstAddr, TunnelStorage storage) protected abstract void Send(byte[] packet, int packetLen); - protected void UpdateLastActivity() + public void UpdateLastActivity() { LastActivity = DateTime.UtcNow; } diff --git a/AutoTunnel/ClientSender.cs b/AutoTunnel/ClientSender.cs index c9c7676..5a7fef4 100644 --- a/AutoTunnel/ClientSender.cs +++ b/AutoTunnel/ClientSender.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using System.Net; using System.Net.Sockets; using System.Threading; @@ -27,10 +26,13 @@ public class ClientSender : BaseSender private bool _isInited; - public ClientSender(string dstAddr, IPEndPoint remoteEP, byte[] serverKey, TunnelStorage storage) + public readonly IPEndPoint RemoteEP; + + public ClientSender(IPAddress dstAddr, IPEndPoint remoteEP, byte[] serverKey, TunnelStorage storage) : base(dstAddr, storage) { Console.WriteLine("Tunnel watcher was created for " + dstAddr); + RemoteEP = remoteEP; _packetWriter = new PacketWriter(); _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); _socket.Connect(remoteEP); diff --git a/AutoTunnel/Config/ListeningConfig.cs b/AutoTunnel/Config/ListeningConfig.cs new file mode 100644 index 0000000..6659ae7 --- /dev/null +++ b/AutoTunnel/Config/ListeningConfig.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Force.AutoTunnel.Config +{ + public class ListeningConfig + { + + } +} diff --git a/AutoTunnel/Config/MainConfig.cs b/AutoTunnel/Config/MainConfig.cs new file mode 100644 index 0000000..c10829d --- /dev/null +++ b/AutoTunnel/Config/MainConfig.cs @@ -0,0 +1,21 @@ +using System.ComponentModel; + +using Newtonsoft.Json; + +namespace Force.AutoTunnel.Config +{ + public class MainConfig + { + [DefaultValue(true)] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + public bool EnableListening { get; set; } + + [DefaultValue(true)] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + public bool AddFirewallRule { get; set; } + + public string[] Keys { get; set; } + + public RemoteServerConfig[] RemoteServers { get; set; } + } +} diff --git a/AutoTunnel/Config/RemoteServerConfig.cs b/AutoTunnel/Config/RemoteServerConfig.cs new file mode 100644 index 0000000..6344844 --- /dev/null +++ b/AutoTunnel/Config/RemoteServerConfig.cs @@ -0,0 +1,9 @@ +namespace Force.AutoTunnel.Config +{ + public class RemoteServerConfig + { + public string Address { get; set; } + + public string Key { get; set; } + } +} diff --git a/AutoTunnel/Encryption/PasswordHelper.cs b/AutoTunnel/Encryption/PasswordHelper.cs index ee13d45..84ba7b2 100644 --- a/AutoTunnel/Encryption/PasswordHelper.cs +++ b/AutoTunnel/Encryption/PasswordHelper.cs @@ -23,14 +23,14 @@ public static byte[] Encrypt(string publicRsaKey, byte[] data) { var rsa = new RSACryptoServiceProvider(); rsa.FromXmlString(publicRsaKey); - return rsa.Encrypt(data, true); + return rsa.Encrypt(data, false); } public static byte[] Decrypt(string privateRsaKey, byte[] data) { var rsa = new RSACryptoServiceProvider(); rsa.FromXmlString(privateRsaKey); - return rsa.Decrypt(data, true); + return rsa.Decrypt(data, false); } } } diff --git a/AutoTunnel/Listener.cs b/AutoTunnel/Listener.cs index 8678a01..cefbe4e 100644 --- a/AutoTunnel/Listener.cs +++ b/AutoTunnel/Listener.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.Net; using System.Net.Sockets; using System.Threading; @@ -13,19 +12,20 @@ public class Listener { private readonly TunnelStorage _storage; - private readonly byte[] _serverKey; + private readonly byte[][] _serverKeys; private readonly PacketWriter _packetWriter; - public Listener(TunnelStorage storage, byte[] serverKey) + public Listener(TunnelStorage storage, byte[][] serverKeys) { _storage = storage; - _serverKey = serverKey; + _serverKeys = serverKeys; _packetWriter = new PacketWriter(); } public void Start() { + Console.WriteLine("Started listening for incoming connections"); Task.Factory.StartNew(StartInternal); Task.Factory.StartNew(CleanupThread); } @@ -34,7 +34,13 @@ private void CleanupThread() { while (true) { - _storage.RemoveOldSenders(TimeSpan.FromMinutes(10)); + var oldSenders = _storage.GetOldSenders(TimeSpan.FromMinutes(10)); + foreach (var os in oldSenders) + { + Console.WriteLine("Removing idle session " + os.Address + ":" + os.Port); + _storage.Remove(os); + } + Thread.Sleep(10 * 60 * 1000); } } @@ -46,7 +52,6 @@ private void StartInternal() { s.Bind(new IPEndPoint(IPAddress.Any, 12017)); byte[] inBuf = new byte[65536]; - byte[] decBuf = null; EndPoint ep1 = new IPEndPoint(IPAddress.Any, 0); while (true) { @@ -56,17 +61,37 @@ private void StartInternal() IPEndPoint ep = (IPEndPoint)ep1; if (cnt == 0) continue; + byte[] decBuf = null; if (cnt % 16 != 0) { if (inBuf[0] == 1) { Console.WriteLine("Estabilishing connection from " + ep.Address + ":" + ep.Port); - var decryptHelper = new DecryptHelper(_serverKey); - var dataLen = decryptHelper.Decrypt(inBuf, 4); + int dataLen = -1; + DecryptHelper decryptHelper = null; + EncryptHelper encryptHelper = null; + foreach (var serverKey in _serverKeys) + { + decryptHelper = new DecryptHelper(serverKey); + dataLen = decryptHelper.Decrypt(inBuf, 4); + if (dataLen > 0) + { + encryptHelper = new EncryptHelper(serverKey); + break; + } + } + + // data is invalid, do not reply + if (dataLen < 0) + { + Console.WriteLine("Invalid data from " + ep.Address + ":" + ep.Port); + continue; + } + var serverHandshake = new ServerHandshake(); var outPacket = serverHandshake.GetOutPacket(decryptHelper.InnerBuf, dataLen); _storage.SetNewEndPoint(serverHandshake.SessionKey, ep); - var encryptHelper = new EncryptHelper(_serverKey); + var outLen = encryptHelper.Encrypt(outPacket, outPacket.Length); var initBuf = new byte[outLen + 4]; Buffer.BlockCopy(encryptHelper.InnerBuf, 0, initBuf, 4, outLen); @@ -112,19 +137,22 @@ private void StartInternal() cnt = len; } - var sourceIp = decBuf[12] + "." + decBuf[13] + "." + decBuf[14] + "." + decBuf[15]; + // var sourceIp = decBuf[12] + "." + decBuf[13] + "." + decBuf[14] + "." + decBuf[15]; + var sourceIp = new IPAddress(decBuf[12] | (decBuf[13] << 8) | (decBuf[14] << 16) | (decBuf[15] << 24)); // if we already has option to estabilish connection to this ip, do not add additional sender if (!_storage.OutgoingConnectionAdresses.Contains(sourceIp)) { var sender = _storage.GetOrAddSender(ep, () => new ReplySender(sourceIp, s, ep, _storage)); + sender.UpdateLastActivity(); // ip was changed for client - if (sender.DstAddr != sourceIp) + if (!sender.DstAddr.Equals(sourceIp)) { Console.WriteLine("Remote endpoint " + ep.Address + ":" + ep.Port + " has changed ip: " + sender.DstAddr + "->" + sourceIp); + s.SendTo(new byte[] { 0x3, 0, 0, 0 }, 4, SocketFlags.None, ep); sender.Dispose(); _storage.Remove(ep); - _storage.GetOrAddSender(ep, () => new ReplySender(sourceIp, s, ep, _storage)); + // _storage.GetOrAddSender(ep, () => new ReplySender(sourceIp, s, ep, _storage)); } } diff --git a/AutoTunnel/Program.cs b/AutoTunnel/Program.cs index e00b315..9ba412c 100644 --- a/AutoTunnel/Program.cs +++ b/AutoTunnel/Program.cs @@ -1,14 +1,39 @@ using System; +using System.IO; +using System.Linq; using System.Net; using System.Threading; +using Force.AutoTunnel.Config; + +using Newtonsoft.Json; + namespace Force.AutoTunnel { public class Program { public static void Main(string[] args) { - var serverKey = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 }; + 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)))); + } + catch (Exception ex) + { + Console.Error.WriteLine("Error in parsing config: " + ex.Message); + return; + } + + var serverKeys = (config.Keys ?? new string[0]).Select(PasswordHelper.GenerateKey).ToArray(); if (!NativeHelper.IsNativeAvailable) { Console.Error.WriteLine("Cannot load WinDivert library"); @@ -16,19 +41,20 @@ public static void Main(string[] args) } var ts = new TunnelStorage(); - var l = new Listener(ts, serverKey); - l.Start(); - var targetIp = args.Length > 0 ? args[0] : null; + if (config.EnableListening) + { + if (config.AddFirewallRule) + FirewallHelper.AddOpenFirewallRule("12017"); + + var l = new Listener(ts, serverKeys); + l.Start(); + } - if (targetIp != null) + foreach (var rs in config.RemoteServers ?? new RemoteServerConfig[0]) { - var endpoint = new IPEndPoint(IPAddress.Parse(targetIp), 12017); - // adding fake tunnel info - ts.SetNewEndPoint(new byte[16], endpoint); - ts.OutgoingConnectionAdresses.Add(targetIp); - var sender = new ClientSender(targetIp, endpoint, serverKey, ts); - // var sender = ts.GetOrAddSender(targetIp, () => new ClientSender(targetIp, endpoint, serverKey)); + var endpoint = new IPEndPoint(IPAddress.Parse(rs.Address), 12017); + ts.AddClientSender(new ClientSender(endpoint.Address, endpoint, PasswordHelper.GenerateKey(rs.Key), ts)); } Thread.Sleep(-1); diff --git a/AutoTunnel/ReplySender.cs b/AutoTunnel/ReplySender.cs index 061602d..e04545f 100644 --- a/AutoTunnel/ReplySender.cs +++ b/AutoTunnel/ReplySender.cs @@ -14,7 +14,7 @@ public class ReplySender : BaseSender private readonly IPEndPoint _remoteEP; - public ReplySender(string dstAddr, Socket socket, IPEndPoint remoteEP, TunnelStorage storage) + public ReplySender(IPAddress dstAddr, Socket socket, IPEndPoint remoteEP, TunnelStorage storage) : base(dstAddr, storage) { Console.WriteLine("Tunnel watcher was created for " + dstAddr); diff --git a/AutoTunnel/TunnelStorage.cs b/AutoTunnel/TunnelStorage.cs index c0f5b16..f8eccf3 100644 --- a/AutoTunnel/TunnelStorage.cs +++ b/AutoTunnel/TunnelStorage.cs @@ -12,7 +12,9 @@ public class TunnelStorage { private readonly ConcurrentDictionary _clients = new ConcurrentDictionary(); - public readonly HashSet OutgoingConnectionAdresses = new HashSet(); + public readonly HashSet OutgoingConnectionAdresses = new HashSet(); + + private readonly List _clientSenders = new List(); public BaseSender GetOrAddSender(IPEndPoint remoteHost, Func creatorFunc) { @@ -42,11 +44,13 @@ private bool Remove(ulong hostKey) return false; } - public void RemoveOldSenders(TimeSpan killTime) + public IPEndPoint[] GetOldSenders(TimeSpan killTime) { var dt = DateTime.UtcNow; - var toRemove = (from client in _clients where client.Value is ReplySender && dt.Subtract(client.Value.LastActivity) >= killTime select client.Key).ToList(); - toRemove.ForEach(x => Remove(x)); + var toRemove = (from client in _clients where client.Value is ReplySender && dt.Subtract(client.Value.LastActivity) >= killTime select client.Key) + .Select(x => new IPEndPoint((long)(x >> 16), (int)(x & 0xffff))) + .ToArray(); + return toRemove; } private readonly ConcurrentDictionary _sessionKeys = new ConcurrentDictionary(); @@ -76,12 +80,23 @@ public DecryptHelper GetSessionDecryptor(IPEndPoint remoteHost) private static ulong GetHostKey(IPEndPoint endpoint) { +#pragma warning disable 612,618 return ((ulong)endpoint.Address.Address << 16) | (uint)endpoint.Port; +#pragma warning restore 612,618 } public bool HasSession(ulong key) { return _sessionKeys.ContainsKey(key); } + + public void AddClientSender(ClientSender sender) + { + // adding fake tunnel info + SetNewEndPoint(new byte[16], sender.RemoteEP); + OutgoingConnectionAdresses.Add(sender.DstAddr); + + _clientSenders.Add(sender); + } } } diff --git a/AutoTunnel/config.json b/AutoTunnel/config.json new file mode 100644 index 0000000..9144f9f --- /dev/null +++ b/AutoTunnel/config.json @@ -0,0 +1,6 @@ +{ + "keys": ["key1"], + "remoteServers": [ + { "address": "192.168.16.8", "key": "key1", keepAlive: true, connectOnStart: true } + ] +} \ No newline at end of file diff --git a/AutoTunnel/packages.config b/AutoTunnel/packages.config new file mode 100644 index 0000000..7c276ed --- /dev/null +++ b/AutoTunnel/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From 66a8b0d8a54187b14105de0bdbf51fb53c5e811a Mon Sep 17 00:00:00 2001 From: force Date: Thu, 31 Aug 2017 00:10:09 +0300 Subject: [PATCH 06/15] improvements and bug fixes (ability to set ports, logging, auto connect, keep alive --- AutoTunnel/AutoTunnel.csproj | 7 +- AutoTunnel/BaseSender.cs | 41 ++++++-- AutoTunnel/ClientSender.cs | 57 +++++++---- AutoTunnel/Config/MainConfig.cs | 10 ++ AutoTunnel/Config/RemoteServerConfig.cs | 4 + AutoTunnel/Encryption/EncryptHelper.cs | 2 - AutoTunnel/Encryption/PasswordHelper.cs | 2 +- AutoTunnel/EndpointHelper.cs | 27 +++++ AutoTunnel/Listener.cs | 64 ++++++------ AutoTunnel/Logging/AggregateLog.cs | 28 ++++++ AutoTunnel/Logging/ConsoleLog.cs | 13 +++ AutoTunnel/Logging/FileLog.cs | 26 +++++ .../ListeningConfig.cs => Logging/ILog.cs} | 6 +- AutoTunnel/Logging/LogHelper.cs | 27 +++++ AutoTunnel/Program.cs | 22 +++-- AutoTunnel/ReplySender.cs | 17 ++-- AutoTunnel/TunnelStorage.cs | 99 +++++++++---------- AutoTunnel/config.json | 2 +- 18 files changed, 325 insertions(+), 129 deletions(-) create mode 100644 AutoTunnel/EndpointHelper.cs create mode 100644 AutoTunnel/Logging/AggregateLog.cs create mode 100644 AutoTunnel/Logging/ConsoleLog.cs create mode 100644 AutoTunnel/Logging/FileLog.cs rename AutoTunnel/{Config/ListeningConfig.cs => Logging/ILog.cs} (52%) create mode 100644 AutoTunnel/Logging/LogHelper.cs diff --git a/AutoTunnel/AutoTunnel.csproj b/AutoTunnel/AutoTunnel.csproj index 2d64c8d..1ed9491 100644 --- a/AutoTunnel/AutoTunnel.csproj +++ b/AutoTunnel/AutoTunnel.csproj @@ -60,8 +60,13 @@ - + + + + + + diff --git a/AutoTunnel/BaseSender.cs b/AutoTunnel/BaseSender.cs index 35b4ca7..ea3eb6f 100644 --- a/AutoTunnel/BaseSender.cs +++ b/AutoTunnel/BaseSender.cs @@ -1,7 +1,11 @@ using System; using System.Net; +using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Tasks; +using Force.AutoTunnel.Logging; + namespace Force.AutoTunnel { public abstract class BaseSender : IDisposable @@ -10,22 +14,33 @@ public abstract class BaseSender : IDisposable private bool _isExiting; - public readonly IPAddress DstAddr; + protected IPAddress DstAddr { get; set; } public DateTime LastActivity { get; private set; } - private readonly TunnelStorage _storage; + protected readonly TunnelStorage Storage; + + public TunnelStorage.Session Session { get; set; } - protected BaseSender(IPAddress dstAddr, TunnelStorage storage) + protected BaseSender(TunnelStorage.Session session, IPAddress watchAddr, TunnelStorage storage) { - _storage = storage; - DstAddr = dstAddr; + Storage = storage; + Session = session; - // or (udp and udp.DstPort != 12017) - _handle = WinDivert.WinDivertOpen("outbound and ip and (ip.DstAddr == " + DstAddr + ")", WinDivert.LAYER_NETWORK, 0, 0); + ReInitDivert(watchAddr); Task.Factory.StartNew(StartInternal); } + protected void ReInitDivert(IPAddress newDstAddr) + { + DstAddr = newDstAddr; + if (_handle != IntPtr.Zero && _handle != (IntPtr)(-1)) + WinDivert.WinDivertClose(_handle); + + // or (udp and udp.DstPort != 12017) + _handle = WinDivert.WinDivertOpen("outbound and ip and (ip.DstAddr == " + newDstAddr + ")", WinDivert.LAYER_NETWORK, 0, 0); + } + protected abstract void Send(byte[] packet, int packetLen); public void UpdateLastActivity() @@ -38,14 +53,20 @@ private void StartInternal() byte[] packet = new byte[65536]; WinDivert.WinDivertAddress addr = new WinDivert.WinDivertAddress(); int packetLen = 0; - while (!_isExiting && WinDivert.WinDivertRecv(_handle, packet, packet.Length, ref addr, ref packetLen)) + while (!_isExiting) { + if (!WinDivert.WinDivertRecv(_handle, packet, packet.Length, ref addr, ref packetLen)) + { + LogHelper.Log.WriteLine("Cannot receive network data: " + Marshal.GetLastWin32Error()); + Thread.Sleep(1000); + continue; + } // Console.WriteLine("Recv: " + packet[16] + "." + packet[17] + "." + packet[18] + "." + packet[19] + ":" + (packet[23] | ((uint)packet[22] << 8))); if (packet[9] == 17) { var key = ((ulong)(packet[16] | ((uint)packet[17] << 8) | ((uint)packet[18] << 16) | (((uint)packet[19]) << 24)) << 16) | (packet[23] | ((uint)packet[22] << 8)); // do not catch this packet, it is our tunnel to other computer - if (_storage.HasSession(key)) + if (Storage.HasSession(key)) { var writeLen = 0; WinDivert.WinDivertSend(_handle, packet, packetLen, ref addr, ref writeLen); @@ -59,7 +80,7 @@ private void StartInternal() } catch (Exception ex) { - Console.WriteLine(ex.Message); + LogHelper.Log.WriteLine(ex); } } } diff --git a/AutoTunnel/ClientSender.cs b/AutoTunnel/ClientSender.cs index 5a7fef4..66311ea 100644 --- a/AutoTunnel/ClientSender.cs +++ b/AutoTunnel/ClientSender.cs @@ -1,16 +1,17 @@ using System; -using System.Net; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; +using Force.AutoTunnel.Config; using Force.AutoTunnel.Encryption; +using Force.AutoTunnel.Logging; namespace Force.AutoTunnel { public class ClientSender : BaseSender { - private readonly Socket _socket; + private Socket _socket; private bool _disposed; @@ -26,21 +27,23 @@ public class ClientSender : BaseSender private bool _isInited; - public readonly IPEndPoint RemoteEP; + private readonly RemoteServerConfig _config; - public ClientSender(IPAddress dstAddr, IPEndPoint remoteEP, byte[] serverKey, TunnelStorage storage) - : base(dstAddr, storage) + public ClientSender(RemoteServerConfig config, TunnelStorage storage) + : base(null, EndpointHelper.ParseEndPoint(config.Address, 1).Address, storage) { - Console.WriteLine("Tunnel watcher was created for " + dstAddr); - RemoteEP = remoteEP; + storage.OutgoingConnectionAdresses.Add(DstAddr); + _config = config; + _serverKey = PasswordHelper.GenerateKey(config.Key); _packetWriter = new PacketWriter(); - _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - _socket.Connect(remoteEP); - _serverKey = serverKey; + LogHelper.Log.WriteLine("Tunnel watcher was created for " + config.Address); Task.Factory.StartNew(ReceiveCycle); - // Task.Factory.StartNew(PingCycle); + if (config.KeepAlive) + Task.Factory.StartNew(PingCycle); + if (config.ConnectOnStart) + Init(); } private int _isIniting; @@ -65,7 +68,17 @@ private void InitInternal() _encryptHelper = new EncryptHelper(_serverKey); _decryptHelper = new DecryptHelper(_serverKey); - Console.WriteLine("Initializing connection to " + DstAddr); + var ep = EndpointHelper.ParseEndPoint(_config.Address, 12017); + if (!ep.Address.Equals(DstAddr)) + { + Storage.OutgoingConnectionAdresses.Remove(DstAddr); + ReInitDivert(ep.Address); + Storage.OutgoingConnectionAdresses.Add(DstAddr); + } + + Storage.SetNewEndPoint(new byte[16], ep); + + LogHelper.Log.WriteLine("Initializing connection to " + ep); var lenToSend = _encryptHelper.Encrypt(cs.SendingPacket, sendingPacketLen); var packetToSend = _encryptHelper.InnerBuf; @@ -75,7 +88,14 @@ private void InitInternal() initBuf[1] = 1; // version var recLength = 0; + // killing old socket + if (_socket != null) + _socket.Dispose(); + _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + _socket.Connect(ep); + Task task = null; + var period = 2000; while (true) { _socket.Send(initBuf, initBuf.Length, SocketFlags.None); @@ -84,10 +104,12 @@ private void InitInternal() recLength = _socket.Receive(_receiveBuffer); Console.WriteLine(sw.ElapsedMilliseconds);*/ - if (task.Wait(2000)) + if (task.Wait(period)) break; - Console.WriteLine("No response from server " + DstAddr); + period = Math.Min(period + 2000, 1000 * 60); + + LogHelper.Log.WriteLine("No response from server " + ep); } if (recLength < 4 || _receiveBuffer[0] != 0x2) @@ -106,7 +128,7 @@ private void InitInternal() var sessionKey = cs.GetPacketFromServer(_decryptHelper.InnerBuf, decLen); _encryptHelper = new EncryptHelper(sessionKey); _decryptHelper = new DecryptHelper(sessionKey); - Console.WriteLine("Initialized connection to " + DstAddr); + LogHelper.Log.WriteLine("Initialized connection to " + ep); _isInited = true; _initingEvent.Set(); } @@ -120,7 +142,8 @@ private void PingCycle() { while (!_disposed) { - Send(new byte[0], 0); + if (_isInited) + _socket.Send(new byte[] { 0x5, 0, 0, 0 }, 4, SocketFlags.None); Thread.Sleep(15 * 1000); } } @@ -161,7 +184,7 @@ private void ReceiveCycle() // in any case, this is error // if (buf[0] == 0x3) { - Console.WriteLine("Received an error flag from " + DstAddr); + LogHelper.Log.WriteLine("Received an error flag from " + _socket.RemoteEndPoint); _isInited = false; // failed data Init(); diff --git a/AutoTunnel/Config/MainConfig.cs b/AutoTunnel/Config/MainConfig.cs index c10829d..62017b7 100644 --- a/AutoTunnel/Config/MainConfig.cs +++ b/AutoTunnel/Config/MainConfig.cs @@ -16,6 +16,16 @@ public class MainConfig public string[] Keys { get; set; } + [DefaultValue(12017)] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + public int Port { get; set; } + public RemoteServerConfig[] RemoteServers { get; set; } + + [DefaultValue(10 * 60)] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + public int IdleSessionTime { get; set; } + + public string LogFileName { get; set; } } } diff --git a/AutoTunnel/Config/RemoteServerConfig.cs b/AutoTunnel/Config/RemoteServerConfig.cs index 6344844..1b0d3db 100644 --- a/AutoTunnel/Config/RemoteServerConfig.cs +++ b/AutoTunnel/Config/RemoteServerConfig.cs @@ -5,5 +5,9 @@ public class RemoteServerConfig public string Address { get; set; } public string Key { get; set; } + + public bool KeepAlive { get; set; } + + public bool ConnectOnStart { get; set; } } } diff --git a/AutoTunnel/Encryption/EncryptHelper.cs b/AutoTunnel/Encryption/EncryptHelper.cs index 9567f7e..1283ca1 100644 --- a/AutoTunnel/Encryption/EncryptHelper.cs +++ b/AutoTunnel/Encryption/EncryptHelper.cs @@ -13,8 +13,6 @@ public class EncryptHelper private readonly byte[] _headerBuf = new byte[16]; - private int _counter; - public byte[] InnerBuf { get diff --git a/AutoTunnel/Encryption/PasswordHelper.cs b/AutoTunnel/Encryption/PasswordHelper.cs index 84ba7b2..0162962 100644 --- a/AutoTunnel/Encryption/PasswordHelper.cs +++ b/AutoTunnel/Encryption/PasswordHelper.cs @@ -9,7 +9,7 @@ public class PasswordHelper public static byte[] GenerateKey(string password) { // we encrypt all data with random value, so, we can use static password here - return new Rfc2898DeriveBytes(password, "AutoTunnel".Select(x => (byte)x).ToArray(), 4096).GetBytes(16); + return new Rfc2898DeriveBytes(string.IsNullOrEmpty(password) ? "no_password" : password, "AutoTunnel".Select(x => (byte)x).ToArray(), 4096).GetBytes(16); } public static Tuple CreateRsa() diff --git a/AutoTunnel/EndpointHelper.cs b/AutoTunnel/EndpointHelper.cs new file mode 100644 index 0000000..7097297 --- /dev/null +++ b/AutoTunnel/EndpointHelper.cs @@ -0,0 +1,27 @@ +using System; +using System.Linq; +using System.Net; + +namespace Force.AutoTunnel +{ + public static class EndpointHelper + { + public static IPEndPoint ParseEndPoint(string address, int defaultPort) + { + var sepIdx = address.IndexOf(':'); + string host = address; + int port = defaultPort; + if (sepIdx >= 0) + { + host = address.Substring(0, sepIdx); + port = Convert.ToInt32(address.Remove(0, sepIdx + 1)); + } + + IPAddress ipAddress; + if (!IPAddress.TryParse(host, out ipAddress)) + ipAddress = Dns.GetHostAddresses(host).First(); + + return new IPEndPoint(ipAddress, port); + } + } +} diff --git a/AutoTunnel/Listener.cs b/AutoTunnel/Listener.cs index cefbe4e..dd08449 100644 --- a/AutoTunnel/Listener.cs +++ b/AutoTunnel/Listener.cs @@ -1,10 +1,13 @@ using System; +using System.Linq; using System.Net; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; +using Force.AutoTunnel.Config; using Force.AutoTunnel.Encryption; +using Force.AutoTunnel.Logging; namespace Force.AutoTunnel { @@ -14,18 +17,21 @@ public class Listener private readonly byte[][] _serverKeys; + private readonly MainConfig _config; + private readonly PacketWriter _packetWriter; - public Listener(TunnelStorage storage, byte[][] serverKeys) + public Listener(TunnelStorage storage, MainConfig config) { _storage = storage; - _serverKeys = serverKeys; + _config = config; + _serverKeys = config.Keys.Select(PasswordHelper.GenerateKey).ToArray(); _packetWriter = new PacketWriter(); } public void Start() { - Console.WriteLine("Started listening for incoming connections"); + LogHelper.Log.WriteLine("Started listening for incoming connections on " + _config.Port); Task.Factory.StartNew(StartInternal); Task.Factory.StartNew(CleanupThread); } @@ -34,11 +40,11 @@ private void CleanupThread() { while (true) { - var oldSenders = _storage.GetOldSenders(TimeSpan.FromMinutes(10)); - foreach (var os in oldSenders) + var oldSessions = _storage.GetOldSessions(TimeSpan.FromSeconds(_config.IdleSessionTime)); + foreach (var os in oldSessions) { - Console.WriteLine("Removing idle session " + os.Address + ":" + os.Port); - _storage.Remove(os); + LogHelper.Log.WriteLine("Removing idle session: " + os); + _storage.RemoveSession(os); } Thread.Sleep(10 * 60 * 1000); @@ -50,7 +56,7 @@ private void StartInternal() Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); try { - s.Bind(new IPEndPoint(IPAddress.Any, 12017)); + s.Bind(new IPEndPoint(IPAddress.Any, _config.Port)); byte[] inBuf = new byte[65536]; EndPoint ep1 = new IPEndPoint(IPAddress.Any, 0); while (true) @@ -59,6 +65,7 @@ private void StartInternal() { int cnt = s.ReceiveFrom(inBuf, ref ep1); IPEndPoint ep = (IPEndPoint)ep1; + TunnelStorage.Session session; if (cnt == 0) continue; byte[] decBuf = null; @@ -66,7 +73,7 @@ private void StartInternal() { if (inBuf[0] == 1) { - Console.WriteLine("Estabilishing connection from " + ep.Address + ":" + ep.Port); + LogHelper.Log.WriteLine("Estabilishing connection from " + ep); int dataLen = -1; DecryptHelper decryptHelper = null; EncryptHelper encryptHelper = null; @@ -84,7 +91,7 @@ private void StartInternal() // data is invalid, do not reply if (dataLen < 0) { - Console.WriteLine("Invalid data from " + ep.Address + ":" + ep.Port); + LogHelper.Log.WriteLine("Invalid data from " + ep); continue; } @@ -99,42 +106,45 @@ private void StartInternal() initBuf[1] = 1; // version s.SendTo(initBuf, initBuf.Length, SocketFlags.None, ep); - Console.WriteLine("Estabilished connection from " + ep.Address + ":" + ep.Port); + LogHelper.Log.WriteLine("Estabilished connection from " + ep); continue; } if (inBuf[0] == 0x5) // ping { + session = _storage.GetSession(ep); + if (session != null) session.UpdateLastActivity(); continue; } else { // error - Console.WriteLine("Unsupported data from " + ep.Address + ":" + ep.Port); + LogHelper.Log.WriteLine("Unsupported data from " + ep); s.SendTo(new byte[] { 0x3, 0, 0, 0 }, 4, SocketFlags.None, ep); continue; } } else { - var decryptor = _storage.GetSessionDecryptor(ep); - if (decryptor == null) + session = _storage.GetSession(ep); + if (session == null) { s.SendTo(new byte[] { 0x3, 0, 0, 0 }, 4, SocketFlags.None, ep); - Console.WriteLine("Missing decryptor for " + ep.Address + ":" + ep.Port); + LogHelper.Log.WriteLine("Missing decryptor for " + ep); continue; } - var len = decryptor.Decrypt(inBuf, 0); + var len = session.Decryptor.Decrypt(inBuf, 0); if (len < 0) { s.SendTo(new byte[] { 0x3, 0, 0, 0 }, 4, SocketFlags.None, ep); - Console.WriteLine("Unable to decrypt data from " + ep.Address + ":" + ep.Port); + LogHelper.Log.WriteLine("Unable to decrypt data from " + ep); continue; } - decBuf = decryptor.InnerBuf; + decBuf = session.Decryptor.InnerBuf; cnt = len; + session.UpdateLastActivity(); } // var sourceIp = decBuf[12] + "." + decBuf[13] + "." + decBuf[14] + "." + decBuf[15]; @@ -142,17 +152,16 @@ private void StartInternal() // if we already has option to estabilish connection to this ip, do not add additional sender if (!_storage.OutgoingConnectionAdresses.Contains(sourceIp)) { - var sender = _storage.GetOrAddSender(ep, () => new ReplySender(sourceIp, s, ep, _storage)); + var sender = _storage.GetOrAddSender(sourceIp, () => new ReplySender(session, sourceIp, s, _storage)); sender.UpdateLastActivity(); - // ip was changed for client - if (!sender.DstAddr.Equals(sourceIp)) + // session was changed for client, killing it and update data + if (!sender.Session.RemoteEP.Equals(ep)) { - Console.WriteLine("Remote endpoint " + ep.Address + ":" + ep.Port + " has changed ip: " + sender.DstAddr + "->" + sourceIp); - s.SendTo(new byte[] { 0x3, 0, 0, 0 }, 4, SocketFlags.None, ep); - sender.Dispose(); - _storage.Remove(ep); - // _storage.GetOrAddSender(ep, () => new ReplySender(sourceIp, s, ep, _storage)); + Console.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; } } @@ -160,8 +169,7 @@ private void StartInternal() } catch (Exception ex) { - Console.WriteLine(ex.Message); - Console.WriteLine(ex.StackTrace); + LogHelper.Log.WriteLine(ex); break; } } diff --git a/AutoTunnel/Logging/AggregateLog.cs b/AutoTunnel/Logging/AggregateLog.cs new file mode 100644 index 0000000..06e3aea --- /dev/null +++ b/AutoTunnel/Logging/AggregateLog.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Force.AutoTunnel.Logging +{ + public class AggregateLog : ILog + { + private List _logs = new List(); + + public AggregateLog AddLog(ILog log) + { + _logs = _logs.Concat(new[] { log }).ToList(); + return this; + } + + public void ClearLogs() + { + _logs = new List(); + } + + public void WriteLine(string line) + { + _logs.ForEach(x => x.WriteLine(line)); + } + } +} diff --git a/AutoTunnel/Logging/ConsoleLog.cs b/AutoTunnel/Logging/ConsoleLog.cs new file mode 100644 index 0000000..4b5b22b --- /dev/null +++ b/AutoTunnel/Logging/ConsoleLog.cs @@ -0,0 +1,13 @@ +using System; +using System.Globalization; + +namespace Force.AutoTunnel.Logging +{ + public class ConsoleLog : ILog + { + public void WriteLine(string line) + { + Console.WriteLine(DateTime.Now.ToString("[yyyy-MM-dd HH:mm:ss] ", CultureInfo.InvariantCulture) + line); + } + } +} diff --git a/AutoTunnel/Logging/FileLog.cs b/AutoTunnel/Logging/FileLog.cs new file mode 100644 index 0000000..d18005e --- /dev/null +++ b/AutoTunnel/Logging/FileLog.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; + +namespace Force.AutoTunnel.Logging +{ + public class FileLog : ILog + { + private readonly string _fileName; + + public FileLog(string fileName) + { + if (!Path.IsPathRooted(fileName)) + fileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); + _fileName = fileName; + } + + public void WriteLine(string line) + { + File.AppendAllText(_fileName, DateTime.Now.ToString("[yyyy-MM-dd HH:mm:ss] ", CultureInfo.InvariantCulture) + line + Environment.NewLine); + } + } +} diff --git a/AutoTunnel/Config/ListeningConfig.cs b/AutoTunnel/Logging/ILog.cs similarity index 52% rename from AutoTunnel/Config/ListeningConfig.cs rename to AutoTunnel/Logging/ILog.cs index 6659ae7..ed6a426 100644 --- a/AutoTunnel/Config/ListeningConfig.cs +++ b/AutoTunnel/Logging/ILog.cs @@ -3,10 +3,10 @@ using System.Linq; using System.Text; -namespace Force.AutoTunnel.Config +namespace Force.AutoTunnel.Logging { - public class ListeningConfig + public interface ILog { - + void WriteLine(string line); } } diff --git a/AutoTunnel/Logging/LogHelper.cs b/AutoTunnel/Logging/LogHelper.cs new file mode 100644 index 0000000..1b15464 --- /dev/null +++ b/AutoTunnel/Logging/LogHelper.cs @@ -0,0 +1,27 @@ +using System; + +namespace Force.AutoTunnel.Logging +{ + public static class LogHelper + { + private static ILog _log = new ConsoleLog(); + + public static void SetLog(ILog log) + { + _log = log; + } + + public static ILog Log + { + get + { + return _log; + } + } + + public static void WriteLine(this ILog log, Exception ex) + { + log.WriteLine(ex.ToString()); + } + } +} diff --git a/AutoTunnel/Program.cs b/AutoTunnel/Program.cs index 9ba412c..7900f67 100644 --- a/AutoTunnel/Program.cs +++ b/AutoTunnel/Program.cs @@ -1,10 +1,12 @@ using System; +using System.Collections.Generic; +using System.Globalization; using System.IO; -using System.Linq; using System.Net; using System.Threading; using Force.AutoTunnel.Config; +using Force.AutoTunnel.Logging; using Newtonsoft.Json; @@ -26,6 +28,15 @@ public static void Main(string[] args) try { config = new JsonSerializer().Deserialize(new JsonTextReader(new StreamReader(File.OpenRead(configPath)))); + if (config.Keys == null) + config.Keys = new string[0]; + if (config.Keys.Length == 0) + config.EnableListening = false; + + 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) { @@ -33,7 +44,6 @@ public static void Main(string[] args) return; } - var serverKeys = (config.Keys ?? new string[0]).Select(PasswordHelper.GenerateKey).ToArray(); if (!NativeHelper.IsNativeAvailable) { Console.Error.WriteLine("Cannot load WinDivert library"); @@ -45,16 +55,16 @@ public static void Main(string[] args) if (config.EnableListening) { if (config.AddFirewallRule) - FirewallHelper.AddOpenFirewallRule("12017"); + FirewallHelper.AddOpenFirewallRule(config.Port.ToString(CultureInfo.InvariantCulture)); - var l = new Listener(ts, serverKeys); + var l = new Listener(ts, config); l.Start(); } + var clientSenders = new List(); foreach (var rs in config.RemoteServers ?? new RemoteServerConfig[0]) { - var endpoint = new IPEndPoint(IPAddress.Parse(rs.Address), 12017); - ts.AddClientSender(new ClientSender(endpoint.Address, endpoint, PasswordHelper.GenerateKey(rs.Key), ts)); + clientSenders.Add(new ClientSender(rs, ts)); } Thread.Sleep(-1); diff --git a/AutoTunnel/ReplySender.cs b/AutoTunnel/ReplySender.cs index e04545f..3c93613 100644 --- a/AutoTunnel/ReplySender.cs +++ b/AutoTunnel/ReplySender.cs @@ -1,8 +1,8 @@ -using System; -using System.Net; +using System.Net; using System.Net.Sockets; using Force.AutoTunnel.Encryption; +using Force.AutoTunnel.Logging; namespace Force.AutoTunnel { @@ -12,21 +12,18 @@ public class ReplySender : BaseSender private readonly EncryptHelper _encryptHelper; - private readonly IPEndPoint _remoteEP; - - public ReplySender(IPAddress dstAddr, Socket socket, IPEndPoint remoteEP, TunnelStorage storage) - : base(dstAddr, storage) + public ReplySender(TunnelStorage.Session session, IPAddress watchAddr, Socket socket, TunnelStorage storage) + : base(session, watchAddr, storage) { - Console.WriteLine("Tunnel watcher was created for " + dstAddr); + LogHelper.Log.WriteLine("Tunnel watcher was created for " + watchAddr); _socket = socket; - _encryptHelper = new EncryptHelper(storage.GetSessionKey(remoteEP)); - _remoteEP = remoteEP; + _encryptHelper = new EncryptHelper(session.Key); } protected override void Send(byte[] packet, int packetLen) { var len = _encryptHelper.Encrypt(packet, packetLen); - _socket.SendTo(_encryptHelper.InnerBuf, len, SocketFlags.None, _remoteEP); + _socket.SendTo(_encryptHelper.InnerBuf, len, SocketFlags.None, Session.RemoteEP); } } } diff --git a/AutoTunnel/TunnelStorage.cs b/AutoTunnel/TunnelStorage.cs index f8eccf3..71f20d4 100644 --- a/AutoTunnel/TunnelStorage.cs +++ b/AutoTunnel/TunnelStorage.cs @@ -10,71 +10,79 @@ namespace Force.AutoTunnel { public class TunnelStorage { - private readonly ConcurrentDictionary _clients = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _clients = new ConcurrentDictionary(); public readonly HashSet OutgoingConnectionAdresses = new HashSet(); private readonly List _clientSenders = new List(); - public BaseSender GetOrAddSender(IPEndPoint remoteHost, Func creatorFunc) + public BaseSender GetOrAddSender(IPAddress dstAddr, Func creatorFunc) { - return _clients.GetOrAdd(GetHostKey(remoteHost), s => creatorFunc()); +#pragma warning disable 612,618 + return _clients.GetOrAdd(dstAddr.Address, s => creatorFunc()); +#pragma warning restore 612,618 + } + + public IPEndPoint[] GetOldSessions(TimeSpan killTime) + { + var dt = DateTime.UtcNow; + return _sessions.Where(x => dt.Subtract(x.Value.LastActivity) >= killTime) + .Select(x => x.Key) + .Select(x => new IPEndPoint((long)(x >> 16), (int)(x & 0xffff))) + .ToArray(); } - public bool Remove(IPEndPoint remoteHost) + public void RemoveSession(IPEndPoint endPoint) { - var hostKey = GetHostKey(remoteHost); - return Remove(hostKey); + var hostKey = GetHostKey(endPoint); + Session session; + if (_sessions.TryRemove(hostKey, out session)) + _clients.Where(x => x.Value.Session == session).Select(x => x.Key).ToList().ForEach(x => + { + BaseSender value; + if (_clients.TryRemove(x, out value)) + value.Dispose(); + }); } - private bool Remove(ulong hostKey) + public class Session { - BaseSender value; - - if (_clients.TryRemove(hostKey, out value)) + public Session(IPEndPoint remoteEP) { - DecryptHelper helper; - _sessionDecryptors.TryRemove(hostKey, out helper); - byte[] dummy; - _sessionKeys.TryRemove(hostKey, out dummy); - value.Dispose(); - return true; + RemoteEP = remoteEP; + UpdateLastActivity(); } - return false; - } + public IPEndPoint RemoteEP { get; private set; } - public IPEndPoint[] GetOldSenders(TimeSpan killTime) - { - var dt = DateTime.UtcNow; - var toRemove = (from client in _clients where client.Value is ReplySender && dt.Subtract(client.Value.LastActivity) >= killTime select client.Key) - .Select(x => new IPEndPoint((long)(x >> 16), (int)(x & 0xffff))) - .ToArray(); - return toRemove; - } + public byte[] Key { get; set; } - private readonly ConcurrentDictionary _sessionKeys = new ConcurrentDictionary(); + public DecryptHelper Decryptor { get; set; } - private readonly ConcurrentDictionary _sessionDecryptors = new ConcurrentDictionary(); + public DateTime LastActivity { get; private set; } - public void SetNewEndPoint(byte[] sessionKey, IPEndPoint remoteHost) - { - var hostKey = GetHostKey(remoteHost); - _sessionKeys[hostKey] = sessionKey; - _sessionDecryptors[hostKey] = new DecryptHelper(sessionKey); + public void UpdateLastActivity() + { + LastActivity = DateTime.UtcNow; + } } - public byte[] GetSessionKey(IPEndPoint remoteHost) + private readonly ConcurrentDictionary _sessions = new ConcurrentDictionary(); + + public void SetNewEndPoint(byte[] sessionKey, IPEndPoint remoteEP) { - byte[] value; - if (_sessionKeys.TryGetValue(GetHostKey(remoteHost), out value)) return value; - return null; + var hostKey = GetHostKey(remoteEP); + _sessions[hostKey] = new Session(remoteEP) + { + Key = sessionKey, + Decryptor = new DecryptHelper(sessionKey) + }; } - public DecryptHelper GetSessionDecryptor(IPEndPoint remoteHost) + public Session GetSession(IPEndPoint remoteHost) { - DecryptHelper value; - if (_sessionDecryptors.TryGetValue(GetHostKey(remoteHost), out value)) return value; + Session value; + if (_sessions.TryGetValue(GetHostKey(remoteHost), out value)) return value; return null; } @@ -87,16 +95,7 @@ private static ulong GetHostKey(IPEndPoint endpoint) public bool HasSession(ulong key) { - return _sessionKeys.ContainsKey(key); - } - - public void AddClientSender(ClientSender sender) - { - // adding fake tunnel info - SetNewEndPoint(new byte[16], sender.RemoteEP); - OutgoingConnectionAdresses.Add(sender.DstAddr); - - _clientSenders.Add(sender); + return _sessions.ContainsKey(key); } } } diff --git a/AutoTunnel/config.json b/AutoTunnel/config.json index 9144f9f..064b852 100644 --- a/AutoTunnel/config.json +++ b/AutoTunnel/config.json @@ -1,6 +1,6 @@ { "keys": ["key1"], "remoteServers": [ - { "address": "192.168.16.8", "key": "key1", keepAlive: true, connectOnStart: true } + { "address": "192.168.16.8", "key": "key1", "keepAlive": true, "connectOnStart": true } ] } \ No newline at end of file From b9a7b29d899877af85c16cabd56eff23589bbce8 Mon Sep 17 00:00:00 2001 From: force Date: Sun, 3 Sep 2017 20:39:05 +0300 Subject: [PATCH 07/15] ability to work as service (with installation) separated connect addres and send address lot of improvements --- AutoTunnel/AutoTunnel.csproj | 30 +++++++ AutoTunnel/BaseSender.cs | 15 +++- AutoTunnel/ClientSender.cs | 76 ++++++++++++++---- AutoTunnel/Config/MainConfig.cs | 2 +- AutoTunnel/Config/RemoteClientConfig.cs | 14 ++++ AutoTunnel/Config/RemoteServerConfig.cs | 4 +- AutoTunnel/FirewallHelper.cs | 6 +- AutoTunnel/Listener.cs | 49 ++++++++--- AutoTunnel/Program.cs | 51 ++++++++---- AutoTunnel/Service/ConsoleHelper.cs | 44 ++++++++++ AutoTunnel/Service/MainService.cs | 23 ++++++ .../Service/MainServiceInstallHelper.cs | 68 ++++++++++++++++ AutoTunnel/Service/MainServiceInstaller.cs | 25 ++++++ AutoTunnel/Starter.cs | 47 +++++++++++ AutoTunnel/TunnelStorage.cs | 47 ++++++++--- AutoTunnel/app.manifest | 19 +++++ AutoTunnel/config.json | 12 ++- AutoTunnel/credits.txt | 1 + tunnel.png | Bin 0 -> 14567 bytes tunnel_active.png | Bin 0 -> 1034 bytes tunnel_hUB_icon.ico | Bin 0 -> 31230 bytes 21 files changed, 465 insertions(+), 68 deletions(-) create mode 100644 AutoTunnel/Config/RemoteClientConfig.cs create mode 100644 AutoTunnel/Service/ConsoleHelper.cs create mode 100644 AutoTunnel/Service/MainService.cs create mode 100644 AutoTunnel/Service/MainServiceInstallHelper.cs create mode 100644 AutoTunnel/Service/MainServiceInstaller.cs create mode 100644 AutoTunnel/Starter.cs create mode 100644 AutoTunnel/app.manifest create mode 100644 AutoTunnel/credits.txt create mode 100644 tunnel.png create mode 100644 tunnel_active.png create mode 100644 tunnel_hUB_icon.ico diff --git a/AutoTunnel/AutoTunnel.csproj b/AutoTunnel/AutoTunnel.csproj index 1ed9491..0427e57 100644 --- a/AutoTunnel/AutoTunnel.csproj +++ b/AutoTunnel/AutoTunnel.csproj @@ -42,23 +42,34 @@ ..\force.snk + + app.manifest + + + ..\tunnel_hUB_icon.ico + ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll True + + + + PreserveNewest + @@ -81,6 +92,15 @@ + + + Component + + + Component + + + @@ -104,6 +124,16 @@ PreserveNewest + + + tunnel_hUB_icon.ico + + + + + tunnel_active.png + + + \ No newline at end of file diff --git a/AutoTunnel.Proxy.Node/proxy.js b/AutoTunnel.Proxy.Node/proxy.js new file mode 100644 index 0000000..7e9e97a --- /dev/null +++ b/AutoTunnel.Proxy.Node/proxy.js @@ -0,0 +1,101 @@ +const dgram = require('dgram'); + +var logFunc = console.log; + +var start = function (portNumber) { + var server = dgram.createSocket('udp4'); + + var proxyPairs = {}; + var failPairs = {}; + + server.bind(portNumber, function () { + address = server.address(); + logFunc('Server listening ' + address.address + ':' + address.port); + }); + server.on('error', function (err) { + logFunc('Server error: ', err); + }); + + server.on('message', function (msg, ep) { + // try { + // proxy init + if (ep.size % 16 !== 0 && msg[0] === 0x7) { + // invalid data + if (ep.size <= 9) + return; + var len = msg.readInt32LE(4); + var dstHostAndPort = msg.toString('utf8', 8, 8 + len); + var spl = dstHostAndPort.split(':'); + var dstHost = spl[0]; + var dstPort = (spl[1] || 12017) * 1; + var socket = dgram.createSocket('udp4'); + socket.on('message', function (bmsg, bep) { + var pair = proxyPairs[ep.address + ':' + ep.port]; + if (pair) { + pair.lastActivity = new Date().getTime(); + server.send(bmsg, 0, bep.size, ep.port, ep.address); + } + }); + proxyPairs[ep.address + ':' + ep.port] = { + sourceHost: ep.address, + sourcePort: ep.port, + host: dstHost, + port: dstPort, + targetSocket: socket, + lastActivity: new Date().getTime() + }; + logFunc('Estabilished tunnel ', ep.address + ':' + ep.port + '->' + dstHost + ':' + dstPort); + + // TODO: think about answer + } else { + var pair = proxyPairs[ep.address + ':' + ep.port]; + + if (!pair) { + var failPair = failPairs[ep.address + ':' + ep.port]; + // one-time error sending, to add info to sender about 'we do not know who are you' + // sender should reconnect to proxy + if (!failPair) { + logFunc('Missing binding for ', ep.address + ':' + ep.port); + failPairs[ep.address + ':' + ep.port] = { lastActivity: new Date().getTime() }; + var buffer = Buffer.alloc ? Buffer.alloc(4) : new Buffer(4); + buffer.writeUInt32LE(8); + server.send(buffer, 0, 4, ep.port, ep.address); + } + } else { + pair.lastActivity = new Date().getTime(); + pair.targetSocket.send(msg, 0, ep.size, pair.port, pair.host); + } + } + // } catch (e) { + // logFunc('Error in proxy message processing', e); + // } + }); + + setInterval(function () { + var now = new Date().getTime(); + Object.keys(proxyPairs).forEach(function (p) { + var proxyPair = proxyPairs[p]; + if (now - proxyPair.lastActivity > 10 * 60 * 1000) { + proxyPair.targetSocket.close(); + delete proxyPairs[p]; + logFunc('Removing idle session ' + proxyPair.sourceHost + ':' + proxyPair.sourcePort); + } + }); + + Object.keys(failPairs).forEach(function (p) { + var proxyPair = failPairs[p]; + if (now - proxyPair.lastActivity > 10 * 60 * 1000) { + delete failPairs[p]; + } + }); + }, 10 * 60 * 1000); +}; + +module.exports = { + start: function (portNumber) { + return start(portNumber); + }, + setLogger: function (loggerFunc) { + logFunc = loggerFunc; + } +}; \ No newline at end of file diff --git a/AutoTunnel.Proxy.Node/proxyStarter.js b/AutoTunnel.Proxy.Node/proxyStarter.js new file mode 100644 index 0000000..1282106 --- /dev/null +++ b/AutoTunnel.Proxy.Node/proxyStarter.js @@ -0,0 +1,3 @@ +var p = require('./proxy.js'); + +p.start(12018); \ No newline at end of file diff --git a/AutoTunnel.sln b/AutoTunnel.sln index 3f90b5f..4198141 100644 --- a/AutoTunnel.sln +++ b/AutoTunnel.sln @@ -3,6 +3,8 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2012 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTunnel", "AutoTunnel\AutoTunnel.csproj", "{BC409F07-4C77-4930-8597-B9BD5837E672}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTunnel.Proxy.Node", "AutoTunnel.Proxy.Node\AutoTunnel.Proxy.Node.csproj", "{9C40B1D7-BB7E-4B64-9B85-2C106910FF2A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -13,6 +15,10 @@ Global {BC409F07-4C77-4930-8597-B9BD5837E672}.Debug|Any CPU.Build.0 = Debug|Any CPU {BC409F07-4C77-4930-8597-B9BD5837E672}.Release|Any CPU.ActiveCfg = Release|Any CPU {BC409F07-4C77-4930-8597-B9BD5837E672}.Release|Any CPU.Build.0 = Release|Any CPU + {9C40B1D7-BB7E-4B64-9B85-2C106910FF2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9C40B1D7-BB7E-4B64-9B85-2C106910FF2A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9C40B1D7-BB7E-4B64-9B85-2C106910FF2A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9C40B1D7-BB7E-4B64-9B85-2C106910FF2A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/AutoTunnel/AutoTunnel.csproj b/AutoTunnel/AutoTunnel.csproj index 0427e57..f73295a 100644 --- a/AutoTunnel/AutoTunnel.csproj +++ b/AutoTunnel/AutoTunnel.csproj @@ -101,6 +101,7 @@ + @@ -134,6 +135,11 @@ tunnel_active.png + + + tunnel_estabilishing.png + +