Skip to content

Commit

Permalink
Restores multi-threaded packet IO
Browse files Browse the repository at this point in the history
Fixes #11

Clean up.
Stop deleting and recreated events, simply reset them in IO loop.
  • Loading branch information
TechnikEmpire committed Jul 28, 2018
1 parent bc858df commit 6da16c5
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@
<PackageReference Include="WinDivertSharp">
<Version>1.4.3.2</Version>
</PackageReference>
<PackageReference Include="WindowsFirewallHelper">
<Version>1.4.6592.8627</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CitadelCore.Windows\CitadelCore.Windows.csproj">
Expand Down
50 changes: 46 additions & 4 deletions CitadelCore.Windows.Example/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@
using Microsoft.AspNetCore.WebUtilities;
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading;
using WindowsFirewallHelper;

namespace CitadelCoreTest
{
Expand All @@ -37,20 +40,20 @@ private static FirewallResponse OnFirewallCheck(FirewallRequest request)
{
// Let's allow chrome to access TCP 80 and 443, but block all other ports.
Console.WriteLine("Filtering application {0} destined for {1}", request.BinaryAbsolutePath, (ushort)IPAddress.HostToNetworkOrder((short)request.RemotePort));
return new FirewallResponse(FirewallAction.FilterApplication);
return new FirewallResponse(CitadelCore.Net.Proxy.FirewallAction.FilterApplication);
}
else
{
// Let's allow chrome to access TCP 80 and 443, but block all other
// ports. This is where we're blocking any non-80/443 bound transmission.
Console.WriteLine("Blocking internet for application {0} destined for {1}", request.BinaryAbsolutePath, (ushort)IPAddress.HostToNetworkOrder((short)request.RemotePort));
return new FirewallResponse(FirewallAction.BlockInternetForApplication);
return new FirewallResponse(CitadelCore.Net.Proxy.FirewallAction.BlockInternetForApplication);
}
}

// For all other applications, just let them access the internet without filtering.
Console.WriteLine("Not filtering application {0} destined for {1}", request.BinaryAbsolutePath, (ushort)IPAddress.HostToNetworkOrder((short)request.RemotePort));
return new FirewallResponse(FirewallAction.DontFilterApplication);
return new FirewallResponse(CitadelCore.Net.Proxy.FirewallAction.DontFilterApplication);
}

/// <summary>
Expand Down Expand Up @@ -237,8 +240,47 @@ private static void OnStreamedContentInspection(HttpMessageInfo messageInfo, Str
}
}

private static void GrantSelfFirewallAccess()
{
string processName = System.Diagnostics.Process.GetCurrentProcess().ProcessName;
var hostAssembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly();

// We want to delete all rules that match our process name, so we can create new ones
// that we know will work.
var myRules = FirewallManager.Instance.Rules.Where(r => r.Name.Equals(processName, StringComparison.OrdinalIgnoreCase)).ToList();
if (myRules != null)
{
foreach (var rule in myRules)
{
FirewallManager.Instance.Rules.Remove(rule);
}
}

// Allow all inbound and outbound communications from our process.
var inboundRule = FirewallManager.Instance.CreateApplicationRule(
FirewallProfiles.Domain | FirewallProfiles.Private | FirewallProfiles.Public,
processName,
WindowsFirewallHelper.FirewallAction.Allow, hostAssembly.Location
);
inboundRule.Direction = FirewallDirection.Inbound;

FirewallManager.Instance.Rules.Add(inboundRule);

var outboundRule = FirewallManager.Instance.CreateApplicationRule(
FirewallProfiles.Domain | FirewallProfiles.Private | FirewallProfiles.Public,
processName,
WindowsFirewallHelper.FirewallAction.Allow, hostAssembly.Location
);
outboundRule.Direction = FirewallDirection.Outbound;

// Add the rules to the manager, which will commit them to Windows.
FirewallManager.Instance.Rules.Add(outboundRule);
}

private static void Main(string[] args)
{
GrantSelfFirewallAccess();

s_blockPageBytes = File.ReadAllBytes(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "BlockedPage.html"));

// Let the user decide when to quit with ctrl+c.
Expand Down Expand Up @@ -283,7 +325,7 @@ private static void Main(string[] args)
var proxyServer = new WindowsProxyServer(cfg);

// Give it a kick.
proxyServer.Start();
proxyServer.Start(0);

// And you're up and running.
Console.WriteLine("Proxy Running");
Expand Down
14 changes: 9 additions & 5 deletions CitadelCore.Windows/CitadelCore.Windows.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Version>3.0.8</Version>
<Version>3.0.9</Version>
<Title>CitadeCore.Windows</Title>
<Authors>Jesse Nicholson</Authors>
<Company>Technik Empire</Company>
Expand All @@ -11,11 +11,15 @@
<PackageLicenseUrl>https://raw.githubusercontent.com/TechnikEmpire/CitadelCore.Windows/master/LICENSE</PackageLicenseUrl>
<PackageProjectUrl>https://github.com/TechnikEmpire/CitadelCore.Windows</PackageProjectUrl>
<RepositoryUrl>https://github.com/TechnikEmpire/CitadelCore.Windows</RepositoryUrl>
<PackageReleaseNotes>Inherits several critical fixes from WinDivertSharp and CitadelCore.</PackageReleaseNotes>
<PackageReleaseNotes>Inherits the following changes from new CitadelCore version:

Fixes an issue where duplicate headers were being generated.
Fixes an issue where user generated header values could be ignored.
Adds an interface to the Start mechanism of the ProxyServer class where the user can specify the total number of packet IO threads. Defaults to 0, which is auto-select for the platform specific diverter that is actually using this value.</PackageReleaseNotes>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<AssemblyVersion>3.0.8.0</AssemblyVersion>
<FileVersion>3.0.8.0</FileVersion>
<AssemblyVersion>3.0.9.0</AssemblyVersion>
<FileVersion>3.0.9.0</FileVersion>
<RepositoryType>git</RepositoryType>
<PackageTags>proxy, filter, filtering, content filtering, content-filter, websocket proxy, http proxy, https proxy</PackageTags>
</PropertyGroup>
Expand All @@ -39,7 +43,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="CitadelCore" Version="3.0.5" />
<PackageReference Include="CitadelCore" Version="3.0.8" />
<PackageReference Include="WinDivertSharp" Version="1.4.3.2" />
</ItemGroup>

Expand Down
55 changes: 16 additions & 39 deletions CitadelCore.Windows/Diversion/WindowsDiverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ internal class WindowsDiverter : IDiverter
/// </summary>
private List<Thread> m_diversionThreads;

private static readonly IntPtr s_InvalidHandleValue = new IntPtr(-1);

/// <summary>
/// Gets whether or not the diverter is currently active.
/// </summary>
Expand Down Expand Up @@ -209,8 +211,6 @@ public void Start(int numThreads)
return;
}

numThreads = 1;

if (numThreads <= 0)
{
numThreads = Environment.ProcessorCount;
Expand All @@ -227,15 +227,15 @@ public void Start(int numThreads)

m_diversionHandle = WinDivert.WinDivertOpen(mainFilterString, WinDivertLayer.Network, -1000, 0);

if (m_diversionHandle == new IntPtr(-1) || m_diversionHandle == IntPtr.Zero)
if (m_diversionHandle == s_InvalidHandleValue || m_diversionHandle == IntPtr.Zero)
{
// Invalid handle value.
throw new Exception(string.Format("Failed to open main diversion handle. Got Win32 error code {0}.", Marshal.GetLastWin32Error()));
}

m_QUICDropHandle = WinDivert.WinDivertOpen(QUICFilterString, WinDivertLayer.Network, -999, WinDivertOpenFlags.Drop);

if (m_QUICDropHandle == new IntPtr(-1) || m_QUICDropHandle == IntPtr.Zero)
if (m_QUICDropHandle == s_InvalidHandleValue || m_QUICDropHandle == IntPtr.Zero)
{
// Invalid handle value.
throw new Exception(string.Format("Failed to open QUIC diversion handle. Got Win32 error code {0}.", Marshal.GetLastWin32Error()));
Expand Down Expand Up @@ -271,6 +271,14 @@ private void RunDiversion()
NativeOverlapped recvOverlapped;

IntPtr recvEvent = IntPtr.Zero;
recvEvent = WinDivertSharp.WinAPI.Kernel32.CreateEvent(IntPtr.Zero, false, false, IntPtr.Zero);

if (recvEvent == IntPtr.Zero || recvEvent == new IntPtr(-1))
{
LoggerProxy.Default.Error("Failed to initialize receive IO event.");
return;
}

uint recvAsyncIoLen = 0;

bool isLocalIpv4 = false;
Expand All @@ -293,14 +301,7 @@ private void RunDiversion()
recvAsyncIoLen = 0;

recvOverlapped = new NativeOverlapped();

recvEvent = Kernel32.CreateEvent(IntPtr.Zero, false, false, IntPtr.Zero);

if (recvEvent == IntPtr.Zero || recvEvent == new IntPtr(-1))
{
LoggerProxy.Default.Warn("Failed to initialize receive IO event.");
continue;
}
WinAPI.Kernel32.ResetEvent(recvEvent);

recvOverlapped.EventHandle = recvEvent;

Expand All @@ -314,42 +315,24 @@ private void RunDiversion()
if (error != 997)
{
LoggerProxy.Default.Warn(string.Format("Unknown IO error ID {0}while awaiting overlapped result.", error));
Kernel32.CloseHandle(recvEvent);
continue;
}

// 258 == WAIT_TIMEOUT
switch (Kernel32.WaitForSingleObject(recvEvent, 1000))
while (m_running && WinDivertSharp.WinAPI.Kernel32.WaitForSingleObject(recvEvent, 1000) == (uint)WaitForSingleObjectResult.WaitTimeout)
{
case (uint)WaitForSingleObjectResult.WaitObject0:
{
}
break;

case (uint)WaitForSingleObjectResult.WaitTimeout:
{
continue;
}

default:
{
LoggerProxy.Default.Warn(string.Format("Failed to read packet from WinDivert with Win32 error {0}.", Marshal.GetLastWin32Error()));
continue;
}

}

if (!Kernel32.GetOverlappedResult(m_diversionHandle, ref recvOverlapped, ref recvAsyncIoLen, false))
if (!WinDivertSharp.WinAPI.Kernel32.GetOverlappedResult(m_diversionHandle, ref recvOverlapped, ref recvAsyncIoLen, false))
{
LoggerProxy.Default.Warn("Failed to get overlapped result.");
Kernel32.CloseHandle(recvEvent);
continue;
}

recvLength = recvAsyncIoLen;
}

Kernel32.CloseHandle(recvEvent);

if (addr.Impostor)
{
LoggerProxy.Default.Warn("Skipping imposter packet.");
Expand Down Expand Up @@ -518,12 +501,6 @@ private void RunDiversion()
// local machine, changing the destination port appropriately.
var dstAddress = parseResult.IPv4Header.DstAddr;

var bytes = parseResult.IPv4Header.SrcAddr.GetAddressBytes();
if (bytes[0] != 192)
{
var notLocal = true;
}

parseResult.IPv4Header.DstAddr = parseResult.IPv4Header.SrcAddr;
parseResult.IPv4Header.SrcAddr = dstAddress;

Expand Down
16 changes: 16 additions & 0 deletions CitadelCore.Windows/WinAPI/Iphlpapi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

namespace CitadelCore.Windows.WinAPI
{
internal static class Iphlpapi
{
[DllImport("Iphlpapi.dll", EntryPoint = "GetTcp6Table2")]
public static extern int GetTcp6Table2(IntPtr TcpTable, ref int SizePointer, [MarshalAs(UnmanagedType.Bool)] bool Order);

[DllImport("Iphlpapi.dll", EntryPoint = "GetTcpTable2")]
public static extern int GetTcpTable2(IntPtr TcpTable, ref int SizePointer, [MarshalAs(UnmanagedType.Bool)] bool Order);
}
}
51 changes: 51 additions & 0 deletions CitadelCore.Windows/WinAPI/Kernel32.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;
using System.Runtime.InteropServices;
using System.Text;

namespace CitadelCore.Windows.WinAPI
{
internal static class Kernel32
{
[Flags]
public enum ProcessAccessFlags : uint
{
All = 0x001F0FFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
VirtualMemoryOperation = 0x00000008,
VirtualMemoryRead = 0x00000010,
VirtualMemoryWrite = 0x00000020,
DuplicateHandle = 0x00000040,
CreateProcess = 0x000000080,
SetQuota = 0x00000100,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
QueryLimitedInformation = 0x00001000,
Synchronize = 0x00100000
}

[DllImport("kernel32.dll", EntryPoint = "OpenProcess")]
public static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwProcessId);

[DllImport("kernel32.dll", EntryPoint = "QueryFullProcessImageName", CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool QueryFullProcessImageName(System.IntPtr hProcess, uint dwFlags, StringBuilder lpExeName, ref int lpdwSize);

[DllImport("kernel32.dll", EntryPoint = "CloseHandle")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr hObject);

[DllImport("kernel32.dll", EntryPoint = "ResetEvent")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ResetEvent(IntPtr hObject);

[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr CreateIoCompletionPort(IntPtr fileHandle, IntPtr existingCompletionPort, UIntPtr completionKey, uint numberOfConcurrentThreads);

[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr CreateIoCompletionPort(IntPtr fileHandle, IntPtr existingCompletionPort, ref uint completionKey, uint numberOfConcurrentThreads);

[DllImport("kernel32.dll")]
public static extern bool GetQueuedCompletionStatus(IntPtr completionPort, out uint lpNumberOfBytes, out UIntPtr lpCompletionKey, out IntPtr lpOverlapped, uint dwMilliseconds);
}
}
14 changes: 4 additions & 10 deletions CitadelCore.Windows/WinAPI/NetworkTables.cs
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ internal static List<ITcpConnectionInfo> GetTcp6Table()
{
int tableSize = 0;

var result = GetTcp6Table2(IntPtr.Zero, ref tableSize, false);
var result = Iphlpapi.GetTcp6Table2(IntPtr.Zero, ref tableSize, false);

IntPtr tcpTableRecordsPtr = IntPtr.Zero;

Expand All @@ -343,7 +343,7 @@ internal static List<ITcpConnectionInfo> GetTcp6Table()
{
tcpTableRecordsPtr = Marshal.AllocHGlobal(tableSize);

result = GetTcp6Table2(tcpTableRecordsPtr, ref tableSize, false);
result = Iphlpapi.GetTcp6Table2(tcpTableRecordsPtr, ref tableSize, false);

if(result != 0)
{
Expand Down Expand Up @@ -385,7 +385,7 @@ internal static List<ITcpConnectionInfo> GetTcp4Table()
{
int tableSize = 0;

var result = GetTcpTable2(IntPtr.Zero, ref tableSize, false);
var result = Iphlpapi.GetTcpTable2(IntPtr.Zero, ref tableSize, false);

IntPtr tcpTableRecordsPtr = IntPtr.Zero;

Expand All @@ -395,7 +395,7 @@ internal static List<ITcpConnectionInfo> GetTcp4Table()
{
tcpTableRecordsPtr = Marshal.AllocHGlobal(tableSize);

result = GetTcpTable2(tcpTableRecordsPtr, ref tableSize, false);
result = Iphlpapi.GetTcpTable2(tcpTableRecordsPtr, ref tableSize, false);

if(result != 0)
{
Expand Down Expand Up @@ -432,11 +432,5 @@ internal static List<ITcpConnectionInfo> GetTcp4Table()

return fTable;
}

[DllImport("Iphlpapi.dll", EntryPoint = "GetTcp6Table2")]
private static extern int GetTcp6Table2(IntPtr TcpTable, ref int SizePointer, [MarshalAs(UnmanagedType.Bool)] bool Order);

[DllImport("Iphlpapi.dll", EntryPoint = "GetTcpTable2")]
private static extern int GetTcpTable2(IntPtr TcpTable, ref int SizePointer, [MarshalAs(UnmanagedType.Bool)] bool Order);
}
}
Loading

0 comments on commit 6da16c5

Please sign in to comment.