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