diff --git a/KnockKnock.CLI/KnockKnock.CLI.csproj b/KnockKnock.CLI/KnockKnock.CLI.csproj new file mode 100644 index 0000000..a9d049f --- /dev/null +++ b/KnockKnock.CLI/KnockKnock.CLI.csproj @@ -0,0 +1,17 @@ + + + + Exe + net5.0 + + + + + + + + + + + + diff --git a/KnockKnock.CLI/Program.cs b/KnockKnock.CLI/Program.cs new file mode 100644 index 0000000..d7c7d6f --- /dev/null +++ b/KnockKnock.CLI/Program.cs @@ -0,0 +1,40 @@ +using System; +using System.Net; +using System.Management.Automation; + +namespace KnockKnock.CLI +{ + internal class Program + { + static void Main(string[] args) + { + Lib.Manager m = new Lib.Manager(); + Lib.Proxy p = new Lib.Proxy(new IPEndPoint(IPAddress.Any, 1234), new IPEndPoint(IPAddress.Parse("10.200.0.10"), 3389)); + p.Idle += P_Idle; + p.Active += P_Active; + m.AddProxy(p); + //m.AddProxy(new IPEndPoint(IPAddress.Any, 1234), new IPEndPoint(IPAddress.Parse("172.217.14.195"), 80)); + P_Active(p); + Console.WriteLine("Running"); + Console.ReadLine(); + m.Close(); + Console.WriteLine("Good night!"); + } + + private static void P_Active(Lib.Proxy sender) + { + Console.WriteLine("Active!"); + PowerShell ps = PowerShell.Create(); + ps.AddScript("Resume-VM Debian"); + foreach (PSObject result in ps.Invoke()) + { + Console.WriteLine(result); + } + } + + private static void P_Idle(Lib.Proxy sender) + { + Console.WriteLine("Idle"); + } + } +} diff --git a/KnockKnock.Lib/Connection.cs b/KnockKnock.Lib/Connection.cs new file mode 100644 index 0000000..05d3d26 --- /dev/null +++ b/KnockKnock.Lib/Connection.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Net; +using System.Net.Sockets; + +namespace KnockKnock.Lib +{ + internal class Connection + { + public delegate void SocketReceivedEvent(Connection sender, byte[] data); + public delegate void SocketEvent(Connection sender); + + public SocketReceivedEvent Received; + public SocketEvent Closed; + + const int BUFFER_SIZE = 1024; + + Socket socket; + byte[] buffer = new byte[BUFFER_SIZE]; + + public Connection(Socket socket) + { + this.socket = socket; + } + + public void Open() + { + this.socket.BeginReceive(this.buffer, 0, BUFFER_SIZE, SocketFlags.None, this.SocketReceive, null); + } + + public void Close() + { + this.socket.Close(); + } + + public void Send(byte[] data) + { + try + { + this.socket.Send(data); + } + catch + { + this.Closed(this); + } + } + + protected void SocketReceive(IAsyncResult result) + { + int read; + try + { + read = socket.EndReceive(result); + } + catch + { + Closed(this); + return; + } + + if (read == 0) + { + // Connection closed + Closed(this); + } + else + { + byte[] received = new byte[read]; + Array.Copy(this.buffer, 0, received, 0, read); + Received(this, received); + try + { + socket.BeginReceive(this.buffer, 0, BUFFER_SIZE, SocketFlags.None, this.SocketReceive, null); + } + catch + { + Closed(this); + } + } + } + } +} diff --git a/KnockKnock.Lib/ConnectionPool.cs b/KnockKnock.Lib/ConnectionPool.cs new file mode 100644 index 0000000..9052e21 --- /dev/null +++ b/KnockKnock.Lib/ConnectionPool.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KnockKnock.Lib +{ + internal class ConnectionPool + { + public delegate void PoolEvent(ConnectionPool sender); + + public event PoolEvent Closed; + + List connections; + + public ConnectionPool() + { + connections = new List(); + } + + public void Open() + { + foreach (Connection c in this.connections) + { + c.Open(); + } + } + + public void AddConnection(Connection c) + { + this.connections.Add(c); + c.Received += Connection_Receive; + c.Closed += Connection_Closed; + } + + public void RemoveConnection(Connection c) + { + this.connections.Remove(c); + c.Received -= Connection_Receive; + c.Closed -= Connection_Closed; + } + + public void Close() + { + foreach (Connection c in this.connections) + { + c.Close(); + } + } + + protected void Connection_Receive(Connection sender, byte[] data) + { + System.Diagnostics.Debug.WriteLine("Receive " + data.Length + " bytes"); + foreach (Connection c in this.connections) + { + if (c != sender) + { + System.Diagnostics.Debug.WriteLine("Send " + data.Length + " bytes"); + c.Send(data); + } + } + } + + protected void Connection_Closed(Connection sender) + { + System.Diagnostics.Debug.WriteLine("Closed"); + foreach (Connection c in this.connections) + { + if (c != sender) + { + c.Close(); + } + } + Closed(this); + } + } +} diff --git a/KnockKnock.Lib/KnockKnock.Lib.csproj b/KnockKnock.Lib/KnockKnock.Lib.csproj new file mode 100644 index 0000000..f208d30 --- /dev/null +++ b/KnockKnock.Lib/KnockKnock.Lib.csproj @@ -0,0 +1,7 @@ + + + + net5.0 + + + diff --git a/KnockKnock.Lib/Listener.cs b/KnockKnock.Lib/Listener.cs new file mode 100644 index 0000000..2fa823f --- /dev/null +++ b/KnockKnock.Lib/Listener.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Net; +using System.Net.Sockets; + +namespace KnockKnock.Lib +{ + internal class Listener + { + public delegate void ConnectedEvent(Listener sender, Socket socket); + public event ConnectedEvent Connected; + + int port; + Socket listener; + + + public Listener(int port) + { + this.port = port; + } + + public void Start() + { + listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + listener.Bind(new IPEndPoint(IPAddress.Loopback, this.port)); + listener.Listen(); + listener.BeginAccept(this.ListenerAcceptCallback, null); + } + + public void Stop() + { + listener.Close(); + listener = null; + } + + protected void ListenerAcceptCallback(IAsyncResult result) + { + if (listener == null) return; + Socket client = listener.EndAccept(result); + listener.BeginAccept(this.ListenerAcceptCallback, null); + this.Connected(this, client); + } + } +} diff --git a/KnockKnock.Lib/Manager.cs b/KnockKnock.Lib/Manager.cs new file mode 100644 index 0000000..b2f25a6 --- /dev/null +++ b/KnockKnock.Lib/Manager.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Net; +using System.Net.Sockets; + +namespace KnockKnock.Lib +{ + public class Manager + { + List proxies; + + public Manager() + { + proxies = new List(); + } + + public void AddProxy(Proxy p) + { + proxies.Add(p); + p.Start(); + } + + public void Close() + { + foreach (Proxy p in this.proxies) + { + p.Stop(); + } + } + } +} diff --git a/KnockKnock.Lib/Proxy.cs b/KnockKnock.Lib/Proxy.cs new file mode 100644 index 0000000..63ddb64 --- /dev/null +++ b/KnockKnock.Lib/Proxy.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Net; +using System.Net.Sockets; + +namespace KnockKnock.Lib +{ + public class Proxy + { + public delegate void ProxyEvent(Proxy sender); + + public event ProxyEvent Active; + public event ProxyEvent Idle; + + Listener listener; + Upstream upstream; + List connections; + + + public Proxy(IPEndPoint listener, IPEndPoint upstream) + { + this.listener = new Listener(listener.Port); + this.upstream = new Upstream(upstream.Address, upstream.Port); + this.connections = new List(); + } + + public void Start() + { + this.listener.Connected += Listener_Connected; + this.listener.Start(); + } + + public void Stop() + { + foreach (ConnectionPool p in connections) + { + p.Close(); + } + connections.Clear(); + } + + private void Listener_Connected(Listener sender, Socket client_socket) + { + if (this.connections.Count == 0 && this.Active != null) + { + this.Active(this); + } + Socket upstream_socket = this.upstream.Connect(); + + Connection client_conn = new Connection(client_socket); + Connection upstream_conn = new Connection(upstream_socket); + + ConnectionPool cp = new ConnectionPool(); + cp.AddConnection(client_conn); + cp.AddConnection(upstream_conn); + cp.Closed += Connection_Closed; + cp.Open(); + } + + private void Connection_Closed(ConnectionPool sender) + { + connections.Remove(sender); + if (connections.Count == 0 && this.Idle != null) + { + this.Idle(this); + } + } + } +} diff --git a/KnockKnock.Lib/Upstream.cs b/KnockKnock.Lib/Upstream.cs new file mode 100644 index 0000000..76bf3b7 --- /dev/null +++ b/KnockKnock.Lib/Upstream.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Net; +using System.Net.Sockets; + +namespace KnockKnock.Lib +{ + internal class Upstream + { + IPAddress ip; + int port; + + + public Upstream(IPAddress ip, int port) + { + this.ip = ip; + this.port = port; + } + + public Socket Connect() + { + Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + s.Connect(this.ip, this.port); + return s; + } + } +} diff --git a/KnockKnock.Service/KnockKnock.Service.csproj b/KnockKnock.Service/KnockKnock.Service.csproj new file mode 100644 index 0000000..09f4e7c --- /dev/null +++ b/KnockKnock.Service/KnockKnock.Service.csproj @@ -0,0 +1,20 @@ + + + + net6.0 + enable + enable + dotnet-KnockKnock.Service-7A5DE5F7-7176-47CB-A545-C289BBFCF99D + + + + + + + + + + + + + diff --git a/KnockKnock.Service/Program.cs b/KnockKnock.Service/Program.cs new file mode 100644 index 0000000..2e4e406 --- /dev/null +++ b/KnockKnock.Service/Program.cs @@ -0,0 +1,11 @@ +using KnockKnock.Service; + +IHost host = Host.CreateDefaultBuilder(args) + .ConfigureServices(services => + { + services.AddHostedService(); + }) + .UseWindowsService() + .Build(); + +await host.RunAsync(); diff --git a/KnockKnock.Service/Properties/launchSettings.json b/KnockKnock.Service/Properties/launchSettings.json new file mode 100644 index 0000000..7261a2f --- /dev/null +++ b/KnockKnock.Service/Properties/launchSettings.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "KnockKnock.Service": { + "commandName": "Project", + "dotnetRunMessages": true, + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} diff --git a/KnockKnock.Service/Worker.cs b/KnockKnock.Service/Worker.cs new file mode 100644 index 0000000..26db1eb --- /dev/null +++ b/KnockKnock.Service/Worker.cs @@ -0,0 +1,67 @@ +namespace KnockKnock.Service; +using System.Net; +using System.Management.Automation; +using System.Timers; + +public class Worker : BackgroundService +{ + private readonly ILogger _logger; + + public Worker(ILogger logger) + { + _logger = logger; + } + + Lib.Manager manager; + Timer idleTimer; + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + _logger.LogInformation("Starting KnockKnock"); + + manager = new Lib.Manager(); + + _logger.LogInformation("Creating proxy for :1234 -> 10.200.0.10:3389"); + Lib.Proxy p = new Lib.Proxy(new IPEndPoint(IPAddress.Any, 1234), new IPEndPoint(IPAddress.Parse("10.200.0.10"), 3389)); + p.Active += Proxy_Active; + p.Idle += Proxy_Idle; + + _logger.LogInformation("Instantiating Idle Timer"); + idleTimer = new Timer(10000); + idleTimer.Elapsed += IdleTimer_Elapsed; + + _logger.LogInformation("Starting"); + manager.AddProxy(p); + + while (!stoppingToken.IsCancellationRequested) + { + await Task.Delay(1000, stoppingToken); + } + + manager.Close(); + } + + private void IdleTimer_Elapsed(object? sender, ElapsedEventArgs e) + { + idleTimer.Stop(); + _logger.LogDebug("Suspending VM"); + PowerShell ps = PowerShell.Create(); + ps.AddScript("Suspend-VM Debian"); + ps.Invoke(); + } + + private void Proxy_Idle(Lib.Proxy sender) + { + _logger.LogDebug("Idle -- starting idle timer"); + idleTimer.Start(); + } + + private void Proxy_Active(Lib.Proxy sender) + { + idleTimer.Stop(); + _logger.LogDebug("Resuming VM"); + PowerShell ps = PowerShell.Create(); + ps.AddScript("Resume-VM Debian"); + ps.Invoke(); + } +} diff --git a/KnockKnock.Service/appsettings.Development.json b/KnockKnock.Service/appsettings.Development.json new file mode 100644 index 0000000..b2dcdb6 --- /dev/null +++ b/KnockKnock.Service/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/KnockKnock.Service/appsettings.json b/KnockKnock.Service/appsettings.json new file mode 100644 index 0000000..b2dcdb6 --- /dev/null +++ b/KnockKnock.Service/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/KnockKnock.Service/service-install.bat b/KnockKnock.Service/service-install.bat new file mode 100644 index 0000000..e5022ae --- /dev/null +++ b/KnockKnock.Service/service-install.bat @@ -0,0 +1 @@ +sc create "KnockKnock" start=auto binpath="%~d0%~p0\bin\Release\net6.0\KnockKnock.Service.exe" \ No newline at end of file diff --git a/KnockKnock.Service/service-uninstall.bat b/KnockKnock.Service/service-uninstall.bat new file mode 100644 index 0000000..85f52c2 --- /dev/null +++ b/KnockKnock.Service/service-uninstall.bat @@ -0,0 +1,2 @@ +sc stop "KnockKnock" +sc delete "KnockKnock" \ No newline at end of file diff --git a/KnockKnock.sln b/KnockKnock.sln new file mode 100644 index 0000000..d961600 --- /dev/null +++ b/KnockKnock.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31717.71 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KnockKnock.Lib", "KnockKnock.Lib\KnockKnock.Lib.csproj", "{AE473668-717A-422D-A319-E1F88859EF6B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KnockKnock.CLI", "KnockKnock.CLI\KnockKnock.CLI.csproj", "{1AB960B3-F48A-4787-A031-3D938595A2E0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KnockKnock.Service", "KnockKnock.Service\KnockKnock.Service.csproj", "{B82D18F9-BEF8-461F-898B-EE81F486C20D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AE473668-717A-422D-A319-E1F88859EF6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE473668-717A-422D-A319-E1F88859EF6B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE473668-717A-422D-A319-E1F88859EF6B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE473668-717A-422D-A319-E1F88859EF6B}.Release|Any CPU.Build.0 = Release|Any CPU + {1AB960B3-F48A-4787-A031-3D938595A2E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1AB960B3-F48A-4787-A031-3D938595A2E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1AB960B3-F48A-4787-A031-3D938595A2E0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1AB960B3-F48A-4787-A031-3D938595A2E0}.Release|Any CPU.Build.0 = Release|Any CPU + {B82D18F9-BEF8-461F-898B-EE81F486C20D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B82D18F9-BEF8-461F-898B-EE81F486C20D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B82D18F9-BEF8-461F-898B-EE81F486C20D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B82D18F9-BEF8-461F-898B-EE81F486C20D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {98F24DFD-A873-4227-B0EF-6CCD67208132} + EndGlobalSection +EndGlobal