From 2bf981432dad27607137dd10995f43f855be8868 Mon Sep 17 00:00:00 2001 From: Jesse Nicholson Date: Thu, 26 Jul 2018 03:55:54 -0400 Subject: [PATCH] Inherits several fixes from external packages First true "stable" version of 3.0.x due to external issues inherited. --- .../CitadelCore.Windows.Example.csproj | 14 +- .../CitadelCore.Windows.Example.sln | 6 + CitadelCore.Windows.Example/Program.cs | 33 +- .../CitadelCore.Windows.csproj | 31 +- .../Diversion/WindowsDiverter.cs | 615 +++++++++--------- .../Net/Proxy/WindowsProxyServer.cs | 29 +- 6 files changed, 375 insertions(+), 353 deletions(-) diff --git a/CitadelCore.Windows.Example/CitadelCore.Windows.Example.csproj b/CitadelCore.Windows.Example/CitadelCore.Windows.Example.csproj index b26ede6..245cb37 100644 --- a/CitadelCore.Windows.Example/CitadelCore.Windows.Example.csproj +++ b/CitadelCore.Windows.Example/CitadelCore.Windows.Example.csproj @@ -32,7 +32,7 @@ TRACE prompt 4 - false + true app.manifest @@ -62,12 +62,18 @@ - - 3.0.1 - 2.1.1 + + 1.4.3.2 + + + + + {08845003-f4af-4062-b8c8-fc2b6524144f} + CitadelCore.Windows + \ No newline at end of file diff --git a/CitadelCore.Windows.Example/CitadelCore.Windows.Example.sln b/CitadelCore.Windows.Example/CitadelCore.Windows.Example.sln index e4b3446..685baf3 100644 --- a/CitadelCore.Windows.Example/CitadelCore.Windows.Example.sln +++ b/CitadelCore.Windows.Example/CitadelCore.Windows.Example.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 15.0.27703.2042 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CitadelCore.Windows.Example", "CitadelCore.Windows.Example.csproj", "{6310E947-2EEE-4822-BDBF-59C8CE7F02B7}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CitadelCore.Windows", "..\CitadelCore.Windows\CitadelCore.Windows.csproj", "{08845003-F4AF-4062-B8C8-FC2B6524144F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {6310E947-2EEE-4822-BDBF-59C8CE7F02B7}.Debug|Any CPU.Build.0 = Debug|Any CPU {6310E947-2EEE-4822-BDBF-59C8CE7F02B7}.Release|Any CPU.ActiveCfg = Release|Any CPU {6310E947-2EEE-4822-BDBF-59C8CE7F02B7}.Release|Any CPU.Build.0 = Release|Any CPU + {08845003-F4AF-4062-B8C8-FC2B6524144F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08845003-F4AF-4062-B8C8-FC2B6524144F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08845003-F4AF-4062-B8C8-FC2B6524144F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08845003-F4AF-4062-B8C8-FC2B6524144F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/CitadelCore.Windows.Example/Program.cs b/CitadelCore.Windows.Example/Program.cs index 5206b29..759f04c 100644 --- a/CitadelCore.Windows.Example/Program.cs +++ b/CitadelCore.Windows.Example/Program.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.WebUtilities; using System; using System.IO; +using System.Net; using System.Text; using System.Threading; @@ -22,6 +23,9 @@ internal class Program { private static byte[] s_blockPageBytes; + private static readonly ushort s_standardHttpPortNetworkOrder = (ushort)IPAddress.HostToNetworkOrder((short)80); + private static readonly ushort s_standardHttpsPortNetworkOrder = (ushort)IPAddress.HostToNetworkOrder((short)443); + private static FirewallResponse OnFirewallCheck(FirewallRequest request) { // Only filter chrome. @@ -29,28 +33,23 @@ private static FirewallResponse OnFirewallCheck(FirewallRequest request) if (filtering) { - switch (request.RemotePort) + if (request.RemotePort == s_standardHttpPortNetworkOrder || request.RemotePort == s_standardHttpsPortNetworkOrder) + { + // 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); + } + else { - case 80: - case 443: - { - // 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, request.RemotePort); - return new FirewallResponse(FirewallAction.FilterApplication); - } - - default: - { - // 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, request.RemotePort); - return new FirewallResponse(FirewallAction.BlockInternetForApplication); - } + // 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); } } // For all other applications, just let them access the internet without filtering. - Console.WriteLine("Not filtering application {0} destined for {1}", request.BinaryAbsolutePath, request.RemotePort); + Console.WriteLine("Not filtering application {0} destined for {1}", request.BinaryAbsolutePath, (ushort)IPAddress.HostToNetworkOrder((short)request.RemotePort)); return new FirewallResponse(FirewallAction.DontFilterApplication); } diff --git a/CitadelCore.Windows/CitadelCore.Windows.csproj b/CitadelCore.Windows/CitadelCore.Windows.csproj index 884fa5f..67bf5a9 100644 --- a/CitadelCore.Windows/CitadelCore.Windows.csproj +++ b/CitadelCore.Windows/CitadelCore.Windows.csproj @@ -2,24 +2,20 @@ netstandard2.0 - 3.0.7 + 3.0.8 CitadeCore.Windows Jesse Nicholson Technik Empire Transparent filtering proxy engine for Windows. Copyright 2017-Present Jesse Nicholson - https://www.gnu.org/licenses/lgpl-3.0.en.html + https://raw.githubusercontent.com/TechnikEmpire/CitadelCore.Windows/master/LICENSE https://github.com/TechnikEmpire/CitadelCore.Windows https://github.com/TechnikEmpire/CitadelCore.Windows - Now has ci via travis. -Removed WinDivert, changed license to MPL 2.0 and linked to WinDivertSharp library instead. -No longer requires unsafe code internally. -Cleaned up some unused variables. -Fixes a minor issue with overlapped events not being cleaned up correctly always. + Inherits several critical fixes from WinDivertSharp and CitadelCore. true true - 3.0.7.0 - 3.0.7.0 + 3.0.8.0 + 3.0.8.0 git proxy, filter, filtering, content filtering, content-filter, websocket proxy, http proxy, https proxy @@ -43,25 +39,12 @@ Fixes a minor issue with overlapped events not being cleaned up correctly always - - + + - - - true - build\x86\ - - - - - - true - build\x64\ - - diff --git a/CitadelCore.Windows/Diversion/WindowsDiverter.cs b/CitadelCore.Windows/Diversion/WindowsDiverter.cs index 73346a8..8a30651 100644 --- a/CitadelCore.Windows/Diversion/WindowsDiverter.cs +++ b/CitadelCore.Windows/Diversion/WindowsDiverter.cs @@ -18,7 +18,6 @@ using System.Runtime.InteropServices; using System.Threading; using WinDivertSharp; -using WinDivertSharp.Extensions; using WinDivertSharp.WinAPI; namespace CitadelCore.Windows.Diversion @@ -49,13 +48,13 @@ internal class WindowsDiverter : IDiverter /// Used for tracking which IPV4 TCP connections ought to be forced through the proxy server. /// We use the local port of TCP connections as the index to this array. /// - private readonly int[] m_v4ShouldFilter = new int[ushort.MaxValue]; + private readonly byte[] m_v4ShouldFilter = new byte[ushort.MaxValue]; /// /// Used for tracking which IPV6 TCP connections ought to be forced through the proxy server. /// We use the local port of TCP connections as the index to this array. /// - private readonly int[] m_v6ShouldFilter = new int[ushort.MaxValue]; + private readonly byte[] m_v6ShouldFilter = new byte[ushort.MaxValue]; /// /// Used for keeping track of the local port that we are to return packets to after filtering. @@ -88,16 +87,16 @@ internal class WindowsDiverter : IDiverter /// encrypted. Specific to IPv4 connections. /// private readonly bool[] m_v6EncryptionHints = new bool[ushort.MaxValue]; - + /// /// Constant for port 443 TCP aka HTTPS. /// - private readonly ushort s_httpsStandardPort; + private readonly ushort m_httpsStandardPort; /// /// Constant for port 443 TCP aka HTTPS alt port. /// - private readonly ushort s_httpsAltPort; + private readonly ushort m_httpsAltPort; /// /// Our process ID. We use this to ignore packets originating from within our own software. @@ -168,25 +167,25 @@ public FirewallCheckCallback ConfirmDenyFirewallAccess /// public WindowsDiverter(ushort v4httpProxyPort, ushort v4httpsProxyPort, ushort v6httpProxyPort, ushort v6httpsProxyPort) { - m_v4HttpProxyPort = v4httpProxyPort.SwapByteOrder(); - m_v4HttpsProxyPort = v4httpsProxyPort.SwapByteOrder(); + m_v4HttpProxyPort = (ushort)IPAddress.HostToNetworkOrder((short)v4httpProxyPort); + m_v4HttpsProxyPort = (ushort)IPAddress.HostToNetworkOrder((short)v4httpsProxyPort); - m_v6HttpProxyPort = v6httpProxyPort.SwapByteOrder(); - m_v6HttpsProxyPort = v6httpsProxyPort.SwapByteOrder(); + m_v6HttpProxyPort = (ushort)IPAddress.HostToNetworkOrder((short)v6httpProxyPort); + m_v6HttpsProxyPort = (ushort)IPAddress.HostToNetworkOrder((short)v6httpsProxyPort); - // WinDivertSharp does not do automatic byte order swapping like our old build-in - // version did. So, we'll do the swap to network order immediately, if applicable - // (which it always should be) and then move on in life. + // WinDivertSharp does not do automatic byte order swapping like our old build-in version + // did. So, we'll do the swap to network order immediately, if applicable (which it + // always should be) and then move on in life. if (BitConverter.IsLittleEndian) { - s_httpsAltPort = ((ushort)8443).SwapByteOrder(); - s_httpsStandardPort = ((ushort)443).SwapByteOrder(); + m_httpsAltPort = (ushort)IPAddress.HostToNetworkOrder((short)8443); + m_httpsStandardPort = (ushort)IPAddress.HostToNetworkOrder((short)443); } else { - s_httpsAltPort = ((ushort)8443); - s_httpsStandardPort = ((ushort)443); + m_httpsAltPort = ((ushort)8443); + m_httpsStandardPort = ((ushort)443); } } @@ -210,6 +209,8 @@ public void Start(int numThreads) return; } + numThreads = 1; + if (numThreads <= 0) { numThreads = Environment.ProcessorCount; @@ -267,13 +268,6 @@ private void RunDiversion() uint recvLength = 0; - IPv4Header? ipv4Header = null; - IPv6Header? ipv6Header = null; - IcmpV4Header? icmpV4Header = null; - IcmpV6Header? icmpV6Header = null; - TcpHeader? tcpHeader = null; - UdpHeader? udpHeader = null; - NativeOverlapped recvOverlapped; IntPtr recvEvent = IntPtr.Zero; @@ -287,285 +281,331 @@ private void RunDiversion() while (m_running) { - payloadBufferPtr = null; - - recvLength = 0; - addr.Reset(); - modifiedPacket = false; - dropPacket = false; - isLocalIpv4 = false; - recvAsyncIoLen = 0; + try + { + payloadBufferPtr = null; - recvOverlapped = new NativeOverlapped(); + recvLength = 0; + addr.Reset(); + modifiedPacket = false; + dropPacket = false; + isLocalIpv4 = false; + recvAsyncIoLen = 0; - recvEvent = Kernel32.CreateEvent(IntPtr.Zero, false, false, IntPtr.Zero); + recvOverlapped = new NativeOverlapped(); - if (recvEvent == IntPtr.Zero) - { - LoggerProxy.Default.Warn("Failed to initialize receive IO event."); - continue; - } + recvEvent = Kernel32.CreateEvent(IntPtr.Zero, false, false, IntPtr.Zero); - recvOverlapped.EventHandle = recvEvent; + if (recvEvent == IntPtr.Zero || recvEvent == new IntPtr(-1)) + { + LoggerProxy.Default.Warn("Failed to initialize receive IO event."); + continue; + } - #region Packet Reading Code + recvOverlapped.EventHandle = recvEvent; - if (!WinDivert.WinDivertRecvEx(m_diversionHandle, packet, 0, ref addr, ref recvLength, ref recvOverlapped)) - { - var error = Marshal.GetLastWin32Error(); + #region Packet Reading Code - // 997 == ERROR_IO_PENDING - if (error != 997) + if (!WinDivert.WinDivertRecvEx(m_diversionHandle, packet, 0, ref addr, ref recvLength, ref recvOverlapped)) { - LoggerProxy.Default.Warn(string.Format("Unknown IO error ID {0}while awaiting overlapped result.", error)); - Kernel32.CloseHandle(recvEvent); - continue; + var error = Marshal.GetLastWin32Error(); + + // 997 == ERROR_IO_PENDING + 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)) + { + 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)) + { + LoggerProxy.Default.Warn("Failed to get overlapped result."); + Kernel32.CloseHandle(recvEvent); + continue; + } + + recvLength = recvAsyncIoLen; } - // 258 == WAIT_TIMEOUT - while (Kernel32.WaitForSingleObject(recvEvent, 1000) == (int)WaitForSingleObjectResult.WaitTimeout); + Kernel32.CloseHandle(recvEvent); - if (!Kernel32.GetOverlappedResult(m_diversionHandle, ref recvOverlapped, ref recvAsyncIoLen, false)) + if (addr.Impostor) { - LoggerProxy.Default.Warn("Failed to get overlapped result."); - Kernel32.CloseHandle(recvEvent); + LoggerProxy.Default.Warn("Skipping imposter packet."); continue; } - recvLength = recvAsyncIoLen; - } - Kernel32.CloseHandle(recvEvent); - - #endregion Packet Reading Code + #endregion Packet Reading Code - if (addr.Direction == WinDivertDirection.Outbound) - { - WinDivert.WinDivertHelperParsePacket(packet, recvLength, ref ipv4Header, ref ipv6Header, ref icmpV4Header, ref icmpV6Header, ref tcpHeader, ref udpHeader, ref payloadBufferPtr); + if (addr.Direction == WinDivertDirection.Outbound) + { + var parseResult = WinDivert.WinDivertHelperParsePacket(packet, recvLength); - #region New TCP Connection Detection + #region New TCP Connection Detection - if (tcpHeader != null && tcpHeader.Value.Syn > 0) - { - // Brand new outbound connection. Grab the PID of the process holding this - // port and map it. - if (ipv4Header != null) + if (parseResult.IsTcp && parseResult.TcpHeader.Syn > 0) { - var connInfo = GetLocalPacketInfo(tcpHeader.Value.SrcPort, ipv4Header.Value.SrcAddr); + // Brand new outbound connection. Grab the PID of the process holding this + // port and map it. + if (parseResult.IsIPv4) + { + var connInfo = GetLocalPacketInfo(parseResult.TcpHeader.SrcPort, parseResult.IPv4Header.SrcAddr); + + HandleNewTcpConnection(connInfo, ref parseResult.TcpHeader, false); + + // Handle the special case of entirely blocking internet for this application/port. + if (Volatile.Read(ref m_v4ShouldFilter[parseResult.TcpHeader.SrcPort]) == (int)FirewallAction.BlockInternetForApplication) + { + dropPacket = true; + } + } + + if (parseResult.IsIPv6) + { + var connInfo = GetLocalPacketInfo(parseResult.TcpHeader.SrcPort, parseResult.IPv6Header.SrcAddr); + + HandleNewTcpConnection(connInfo, ref parseResult.TcpHeader, true); + + // Handle the special case of entirely blocking internet for this application/port. + if (Volatile.Read(ref m_v6ShouldFilter[parseResult.TcpHeader.SrcPort]) == (int)FirewallAction.BlockInternetForApplication) + { + dropPacket = true; + } + } + } - HandleNewTcpConnection(connInfo, tcpHeader, false); + // Now that we've processed any potentially new connections, let's see if the + // packet belongs to an existing flow that was marked to be blocked. + // Check if this packet belongs to an IPV4 flow marked for blocking. + if (parseResult.IsIPv4) + { // Handle the special case of entirely blocking internet for this application/port. - if (Volatile.Read(ref m_v4ShouldFilter[tcpHeader.Value.SrcPort]) == (int)FirewallAction.BlockInternetForApplication) + if (Volatile.Read(ref m_v4ShouldFilter[parseResult.TcpHeader.SrcPort]) == (int)FirewallAction.BlockInternetForApplication) { dropPacket = true; } } - if (ipv6Header != null) + // Check if this packet belongs to an IPV6 flow marked for blocking. + if (!dropPacket && parseResult.IsIPv6) { - var connInfo = GetLocalPacketInfo(tcpHeader.Value.SrcPort, ipv6Header.Value.SrcAddr); - - HandleNewTcpConnection(connInfo, tcpHeader, true); - // Handle the special case of entirely blocking internet for this application/port. - if (Volatile.Read(ref m_v6ShouldFilter[tcpHeader.Value.SrcPort]) == (int)FirewallAction.BlockInternetForApplication) + if (Volatile.Read(ref m_v6ShouldFilter[parseResult.TcpHeader.SrcPort]) == (int)FirewallAction.BlockInternetForApplication) { dropPacket = true; } } - } - #endregion New TCP Connection Detection + #endregion New TCP Connection Detection - // I put the checks for ipv4 and ipv6 as a double if statement rather than an - // else if because I'm not sure how that would affect dual-mode sockets. Perhaps - // it's possible for both headers to be defined. Probably not, but since I don't - // know, I err on the side of awesome, or uhh, something like that. + // I put the checks for ipv4 and ipv6 as a double if statement rather than an + // else if because I'm not sure how that would affect dual-mode sockets. Perhaps + // it's possible for both headers to be defined. Probably not, but since I don't + // know, I err on the side of awesome, or uhh, something like that. - // We check local packets for TOR/SOCKS packets here. However, if we don't find - // something we want to block on local addresses, then we want to skip these for - // the rest of the filtering and just let them through. + // We check local packets for TOR/SOCKS packets here. However, if we don't find + // something we want to block on local addresses, then we want to skip these for + // the rest of the filtering and just let them through. - if (dropPacket == false && ipv4Header != null && tcpHeader != null) - { - // Let's explain the weird arcane logic here. First, we check if the current - // flow should even be filtered. We do this, because there's a good chance - // that this flow belongs to our proxy's connections, which we never want to - // filter. If we didn't check this, then we would end up setting the - // isLocalIpv4 flag to true on every single one of our proxy's connections, - // and clients would never get packets ever because with that flag set, the - // direction of the packets wouldn't be sorted. - // - // So, we check this, ensure it's actually something we want to filter. Then, - // we check if the packet is destined for a local address. We set the flag - // accordingly, and if true, then we will allow these packets to go out uninterrupted. - // - // If false, who cares. Regardless of true or false, we check to see if this - // is a TOR/SOCKS4/5 proxy CONNECT, and drop it if it is. - // - // Also note, by letting local/private address destined packets go, we also - // solve the problem of private TLS connections using private TLS self signed - // certs, such as logging into one's router. If we didn't do this check and - // let these through, we would break such connections. - - if (Volatile.Read(ref m_v4ShouldFilter[tcpHeader.Value.SrcPort]) == (int)FirewallAction.FilterApplication) + if (dropPacket == false && parseResult.IsIPv4 && parseResult.IsTcp) { - isLocalIpv4 = ipv4Header.Value.DstAddr.IsPrivateIpv4Address(); - - if (isLocalIpv4) + // Let's explain the weird arcane logic here. First, we check if the current + // flow should even be filtered. We do this, because there's a good chance + // that this flow belongs to our proxy's connections, which we never want to + // filter. If we didn't check this, then we would end up setting the + // isLocalIpv4 flag to true on every single one of our proxy's connections, + // and clients would never get packets ever because with that flag set, the + // direction of the packets wouldn't be sorted. + // + // So, we check this, ensure it's actually something we want to filter. Then, + // we check if the packet is destined for a local address. We set the flag + // accordingly, and if true, then we will allow these packets to go out uninterrupted. + // + // If false, who cares. Regardless of true or false, we check to see if this + // is a TOR/SOCKS4/5 proxy CONNECT, and drop it if it is. + // + // Also note, by letting local/private address destined packets go, we also + // solve the problem of private TLS connections using private TLS self signed + // certs, such as logging into one's router. If we didn't do this check and + // let these through, we would break such connections. + + if (Volatile.Read(ref m_v4ShouldFilter[parseResult.TcpHeader.SrcPort]) == (int)FirewallAction.FilterApplication) { -#if !ENGINE_NO_BLOCK_TOR - byte[] payload = null; - if (payloadBufferPtr != null && payloadBufferPtr.Length > 0) - { - payload = payloadBufferPtr.ToArray(); + isLocalIpv4 = parseResult.IPv4Header.DstAddr.IsPrivateIpv4Address(); - if (payload.IsSocksProxyConnect()) + if (isLocalIpv4) + { +#if !ENGINE_NO_BLOCK_TOR + byte[] payload = null; + if (payloadBufferPtr != null && payloadBufferPtr.Length > 0) { - LoggerProxy.Default.Info("Blocking SOCKS proxy connect."); - continue; + payload = payloadBufferPtr.ToArray(); + + if (payload.IsSocksProxyConnect()) + { + LoggerProxy.Default.Info("Blocking SOCKS proxy connect."); + continue; + } } - } #endif + } } } - } - if (dropPacket == false && !isLocalIpv4) - { - if (ipv4Header != null && tcpHeader != null) + if (dropPacket == false && !isLocalIpv4) { - var tmpIpv4Hdr = ipv4Header.Value; - var tmpTcpHdr = tcpHeader.Value; - - if (tcpHeader.Value.SrcPort == m_v4HttpProxyPort || tcpHeader.Value.SrcPort == m_v4HttpsProxyPort) + if (parseResult.IsIPv4 && parseResult.IsTcp) { - // Means that the data is originating from our proxy in response to a - // client's request, which means it was originally meant to go - // somewhere else. We need to reorder the data such as the src and - // destination ports and addresses and divert it back inbound, so it - // appears to be an inbound response from the original external server. + if (parseResult.TcpHeader.SrcPort == m_v4HttpProxyPort || parseResult.TcpHeader.SrcPort == m_v4HttpsProxyPort) + { + // Means that the data is originating from our proxy in response to a + // client's request, which means it was originally meant to go + // somewhere else. We need to reorder the data such as the src and + // destination ports and addresses and divert it back inbound, so it + // appears to be an inbound response from the original external server. - modifiedPacket = true; + modifiedPacket = true; - tmpTcpHdr.SrcPort = Volatile.Read(ref m_v4ReturnPorts[tcpHeader.Value.DstPort]); - addr.Direction = WinDivertDirection.Inbound; + parseResult.TcpHeader.SrcPort = Volatile.Read(ref m_v4ReturnPorts[parseResult.TcpHeader.DstPort]); + addr.Direction = WinDivertDirection.Inbound; - var dstIp = tmpIpv4Hdr.DstAddr; - tmpIpv4Hdr.DstAddr = ipv4Header.Value.SrcAddr; - tmpIpv4Hdr.SrcAddr = dstIp; - } - else - { - // This means outbound traffic has been captured that we know for - // sure is not coming from our proxy in response to a client, but we - // don't know that it isn't the upstream portion of our proxy trying - // to fetch a response on behalf of a connected client. So, we need - // to check if we have a cached result for information about the - // binary generating the outbound traffic for two reasons. - // - // First, we need to ensure that it's not us, obviously. Secondly, we - // need to ensure that the binary has been granted firewall access to - // generate outbound traffic. - - if (Volatile.Read(ref m_v4ShouldFilter[tcpHeader.Value.SrcPort]) == (int)FirewallAction.FilterApplication) + var dstIp = parseResult.IPv4Header.DstAddr; + parseResult.IPv4Header.DstAddr = parseResult.IPv4Header.SrcAddr; + parseResult.IPv4Header.SrcAddr = dstIp; + } + else { - modifiedPacket = true; + // This means outbound traffic has been captured that we know for + // sure is not coming from our proxy in response to a client, but we + // don't know that it isn't the upstream portion of our proxy trying + // to fetch a response on behalf of a connected client. So, we need + // to check if we have a cached result for information about the + // binary generating the outbound traffic for two reasons. + // + // First, we need to ensure that it's not us, obviously. Secondly, we + // need to ensure that the binary has been granted firewall access to + // generate outbound traffic. + + if (Volatile.Read(ref m_v4ShouldFilter[parseResult.TcpHeader.SrcPort]) == (int)FirewallAction.FilterApplication) + { + modifiedPacket = true; - // If the process was identified as a process that is permitted - // to access the internet, and is not a system process or - // ourselves, then we divert its packets back inbound to the - // local machine, changing the destination port appropriately. - var dstAddress = ipv4Header.Value.DstAddr; + // If the process was identified as a process that is permitted + // to access the internet, and is not a system process or + // ourselves, then we divert its packets back inbound to the + // local machine, changing the destination port appropriately. + var dstAddress = parseResult.IPv4Header.DstAddr; - tmpIpv4Hdr.DstAddr = ipv4Header.Value.SrcAddr; - tmpIpv4Hdr.SrcAddr = dstAddress; + var bytes = parseResult.IPv4Header.SrcAddr.GetAddressBytes(); + if (bytes[0] != 192) + { + var notLocal = true; + } - addr.Direction = WinDivertDirection.Inbound; + parseResult.IPv4Header.DstAddr = parseResult.IPv4Header.SrcAddr; + parseResult.IPv4Header.SrcAddr = dstAddress; - Volatile.Write(ref m_v4ReturnPorts[tcpHeader.Value.SrcPort], tcpHeader.Value.DstPort); + addr.Direction = WinDivertDirection.Inbound; - // Unless we know for sure this is an encrypted connection via - // the HTTP port, we should always default to sending to the - // non-encrypted listener. - var encrypted = Volatile.Read(ref m_v4EncryptionHints[tcpHeader.Value.SrcPort]); + Volatile.Write(ref m_v4ReturnPorts[parseResult.TcpHeader.SrcPort], parseResult.TcpHeader.DstPort); - tmpTcpHdr.DstPort = encrypted ? m_v4HttpsProxyPort : m_v4HttpProxyPort; + // Unless we know for sure this is an encrypted connection via + // the HTTP port, we should always default to sending to the + // non-encrypted listener. + var encrypted = Volatile.Read(ref m_v4EncryptionHints[parseResult.TcpHeader.SrcPort]); + + parseResult.TcpHeader.DstPort = encrypted ? m_v4HttpsProxyPort : m_v4HttpProxyPort; + } } } - // Set our main header vars back to modified vals. - ipv4Header = tmpIpv4Hdr; - tcpHeader = tmpTcpHdr; - } - - // The ipV6 version works exactly the same, just with larger storage for the - // larger addresses. Look at the ipv4 version notes for clarification on anything. - if (ipv6Header != null && tcpHeader != null) - { - var tmpIpv6Hdr = ipv6Header.Value; - var tmpTcpHdr = tcpHeader.Value; - - if (tcpHeader.Value.SrcPort == m_v6HttpProxyPort || tcpHeader.Value.SrcPort == m_v6HttpsProxyPort) + // The ipV6 version works exactly the same, just with larger storage for the + // larger addresses. Look at the ipv4 version notes for clarification on anything. + if (parseResult.IsIPv6 && parseResult.IsTcp) { - modifiedPacket = true; - - var x = tcpHeader.Value; + if (parseResult.TcpHeader.SrcPort == m_v6HttpProxyPort || parseResult.TcpHeader.SrcPort == m_v6HttpsProxyPort) + { + modifiedPacket = true; - tmpTcpHdr.SrcPort = Volatile.Read(ref m_v6ReturnPorts[tcpHeader.Value.DstPort]); - addr.Direction = WinDivertDirection.Inbound; + parseResult.TcpHeader.SrcPort = Volatile.Read(ref m_v6ReturnPorts[parseResult.TcpHeader.DstPort]); + addr.Direction = WinDivertDirection.Inbound; - var dstIp = ipv6Header.Value.DstAddr; - tmpIpv6Hdr.DstAddr = ipv6Header.Value.SrcAddr; - tmpIpv6Hdr.SrcAddr = dstIp; - } - else - { - if (Volatile.Read(ref m_v6ShouldFilter[tcpHeader.Value.SrcPort]) == (int)FirewallAction.FilterApplication) + var dstIp = parseResult.IPv6Header.DstAddr; + parseResult.IPv6Header.DstAddr = parseResult.IPv6Header.SrcAddr; + parseResult.IPv6Header.SrcAddr = dstIp; + } + else { - modifiedPacket = true; + if (Volatile.Read(ref m_v6ShouldFilter[parseResult.TcpHeader.SrcPort]) == (int)FirewallAction.FilterApplication) + { + modifiedPacket = true; - // If the process was identified as a process that is permitted - // to access the internet, and is not a system process or - // ourselves, then we divert its packets back inbound to the - // local machine, changing the destination port appropriately. - var dstAddress = ipv6Header.Value.DstAddr; + // If the process was identified as a process that is permitted + // to access the internet, and is not a system process or + // ourselves, then we divert its packets back inbound to the + // local machine, changing the destination port appropriately. + var dstAddress = parseResult.IPv6Header.DstAddr; - tmpIpv6Hdr.DstAddr = ipv6Header.Value.SrcAddr; - tmpIpv6Hdr.SrcAddr = dstAddress; + parseResult.IPv6Header.DstAddr = parseResult.IPv6Header.SrcAddr; + parseResult.IPv6Header.SrcAddr = dstAddress; - addr.Direction = WinDivertDirection.Inbound; + addr.Direction = WinDivertDirection.Inbound; - Volatile.Write(ref m_v6ReturnPorts[tcpHeader.Value.SrcPort], tcpHeader.Value.DstPort); + Volatile.Write(ref m_v6ReturnPorts[parseResult.TcpHeader.SrcPort], parseResult.TcpHeader.DstPort); - // Unless we know for sure this is an encrypted connection via - // the HTTP port, we should always default to sending to the - // non-encrypted listener. - var encrypted = Volatile.Read(ref m_v6EncryptionHints[tcpHeader.Value.SrcPort]); + // Unless we know for sure this is an encrypted connection via + // the HTTP port, we should always default to sending to the + // non-encrypted listener. + var encrypted = Volatile.Read(ref m_v6EncryptionHints[parseResult.TcpHeader.SrcPort]); - tmpTcpHdr.DstPort = encrypted ? m_v6HttpsProxyPort : m_v6HttpProxyPort; + parseResult.TcpHeader.DstPort = encrypted ? m_v6HttpsProxyPort : m_v6HttpProxyPort; + } } } + } // if(!isLocalIpv4) + }// if (addr.Direction == WINDIVERT_DIRECTION_OUTBOUND) + + if (!dropPacket) + { + if (modifiedPacket) + { + var sumsCalculated = WinDivert.WinDivertHelperCalcChecksums(packet, recvLength, ref addr, WinDivertChecksumHelperParam.All); - // Set our main header vars back to modified vals. - ipv6Header = tmpIpv6Hdr; - tcpHeader = tmpTcpHdr; + if (sumsCalculated <= 0) + { + LoggerProxy.Default.Warn("Modified packet reported that no checksums were calculated"); + } } - } // if(!isLocalIpv4) - }// if (addr.Direction == WINDIVERT_DIRECTION_OUTBOUND) - if (dropPacket) - { - LoggerProxy.Default.Warn("Dropping packet."); - continue; + WinDivert.WinDivertSendEx(m_diversionHandle, packet, recvLength, 0, ref addr); + } } - - if (modifiedPacket) + catch (Exception loopException) { - WinDivert.WinDivertHelperCalcChecksums(packet, recvLength, ref addr, WinDivertChecksumHelperParam.All); + LoggerProxy.Default.Error(loopException); } - - WinDivert.WinDivertSendEx(m_diversionHandle, packet, recvLength, 0, ref addr); } // while (m_running) } @@ -607,100 +647,89 @@ public void Stop() /// /// Whether or not this is from an IPV6 connection. /// - private void HandleNewTcpConnection(ITcpConnectionInfo connInfo, TcpHeader? tcpHeader, bool isIpv6) + private void HandleNewTcpConnection(ITcpConnectionInfo connInfo, ref TcpHeader tcpHeader, bool isIpv6) { - if (tcpHeader != null) + if (connInfo != null && connInfo.OwnerPid == m_thisPid) + { + // This is our process. + switch (isIpv6) + { + case true: + { + Volatile.Write(ref m_v6ShouldFilter[tcpHeader.SrcPort], (int)FirewallAction.DontFilterApplication); + } + break; + + case false: + { + Volatile.Write(ref m_v4ShouldFilter[tcpHeader.SrcPort], (int)FirewallAction.DontFilterApplication); + } + break; + } + } + else { - if (connInfo != null && connInfo.OwnerPid == m_thisPid) + FirewallResponse response = null; + if (connInfo == null || connInfo.OwnerPid == 4 || connInfo.OwnerPid == 0) + { + var firewallRequest = new FirewallRequest("SYSTEM", tcpHeader.SrcPort, tcpHeader.DstPort); + response = ConfirmDenyFirewallAccess?.Invoke(firewallRequest); + } + else + { + // No need to null check here, because the above IF catches whenever connInfo + // is null. + var procPath = connInfo.OwnerProcessPath.Length > 0 ? connInfo.OwnerProcessPath : "SYSTEM"; + var firewallRequest = new FirewallRequest(procPath, tcpHeader.SrcPort, tcpHeader.DstPort); + response = ConfirmDenyFirewallAccess?.Invoke(firewallRequest); + } + + if (response == null) { - // This is our process. + // The user couldn't be bothered to give us an answer, so just go ahead and + // let the packet through. + switch (isIpv6) { case true: { - Volatile.Write(ref m_v6ShouldFilter[tcpHeader.Value.SrcPort], (int)FirewallAction.DontFilterApplication); + Volatile.Write(ref m_v6ShouldFilter[tcpHeader.SrcPort], (byte)FirewallAction.DontFilterApplication); + + Volatile.Write(ref m_v6EncryptionHints[tcpHeader.SrcPort], (tcpHeader.DstPort == m_httpsStandardPort || tcpHeader.DstPort == m_httpsAltPort)); } break; case false: { - Volatile.Write(ref m_v4ShouldFilter[tcpHeader.Value.SrcPort], (int)FirewallAction.DontFilterApplication); + Volatile.Write(ref m_v4ShouldFilter[tcpHeader.SrcPort], (byte)FirewallAction.DontFilterApplication); + + Volatile.Write(ref m_v4EncryptionHints[tcpHeader.SrcPort], (tcpHeader.DstPort == m_httpsStandardPort || tcpHeader.DstPort == m_httpsAltPort)); } break; } } else { - FirewallResponse response = null; - if (connInfo == null || connInfo.OwnerPid == 4 || connInfo.OwnerPid == 0) - { - var firewallRequest = new FirewallRequest("SYSTEM", tcpHeader.Value.SrcPort, tcpHeader.Value.DstPort); - response = ConfirmDenyFirewallAccess?.Invoke(firewallRequest); - } - else - { - // No need to null check here, because the above IF catches whenever connInfo - // is null. - var procPath = connInfo.OwnerProcessPath.Length > 0 ? connInfo.OwnerProcessPath : "SYSTEM"; - var firewallRequest = new FirewallRequest(procPath, tcpHeader.Value.SrcPort, tcpHeader.Value.DstPort); - response = ConfirmDenyFirewallAccess?.Invoke(firewallRequest); - } - - if (response == null) - { - // The user couldn't be bothered to give us an answer, so just go ahead and - // let the packet through. - - switch (isIpv6) - { - case true: - { - Volatile.Write(ref m_v6ShouldFilter[tcpHeader.Value.SrcPort], (int)FirewallAction.DontFilterApplication); - - Volatile.Write(ref m_v6EncryptionHints[tcpHeader.Value.SrcPort], (tcpHeader.Value.DstPort == s_httpsStandardPort || tcpHeader.Value.DstPort == s_httpsAltPort)); - } - break; - - case false: - { - Volatile.Write(ref m_v4ShouldFilter[tcpHeader.Value.SrcPort], (int)FirewallAction.DontFilterApplication); - - Volatile.Write(ref m_v4EncryptionHints[tcpHeader.Value.SrcPort], (tcpHeader.Value.DstPort == s_httpsStandardPort || tcpHeader.Value.DstPort == s_httpsAltPort)); - } - break; - } - } - else + switch (isIpv6) { - switch (isIpv6) - { - case true: - { - Volatile.Write(ref m_v6ShouldFilter[tcpHeader.Value.SrcPort], (int)response.Action); + case true: + { + Volatile.Write(ref m_v6ShouldFilter[tcpHeader.SrcPort], (byte)response.Action); - Volatile.Write(ref m_v6EncryptionHints[tcpHeader.Value.SrcPort], response.EncryptedHint ?? (tcpHeader.Value.DstPort == s_httpsStandardPort || tcpHeader.Value.DstPort == s_httpsAltPort)); - } - break; + Volatile.Write(ref m_v6EncryptionHints[tcpHeader.SrcPort], response.EncryptedHint ?? (tcpHeader.DstPort == m_httpsStandardPort || tcpHeader.DstPort == m_httpsAltPort)); + } + break; - case false: - { - Volatile.Write(ref m_v4ShouldFilter[tcpHeader.Value.SrcPort], (int)response.Action); + case false: + { + Volatile.Write(ref m_v4ShouldFilter[tcpHeader.SrcPort], (byte)response.Action); - Volatile.Write(ref m_v4EncryptionHints[tcpHeader.Value.SrcPort], response.EncryptedHint ?? (tcpHeader.Value.DstPort == s_httpsStandardPort || tcpHeader.Value.DstPort == s_httpsAltPort)); - } - break; - } + Volatile.Write(ref m_v4EncryptionHints[tcpHeader.SrcPort], response.EncryptedHint ?? (tcpHeader.DstPort == m_httpsStandardPort || tcpHeader.DstPort == m_httpsAltPort)); + } + break; } } } - else - { - // Somehow we fail to have even a valid TCP header here. Let the connection go - // through, but warn. - LoggerProxy.Default.Warn("TCP header was a null pointer. Allowing packet."); - - Volatile.Write(ref m_v4ShouldFilter[tcpHeader.Value.SrcPort], (int)FirewallAction.DontFilterApplication); - } } private ITcpConnectionInfo GetLocalPacketInfo(ushort localPort, IPAddress localAddress) diff --git a/CitadelCore.Windows/Net/Proxy/WindowsProxyServer.cs b/CitadelCore.Windows/Net/Proxy/WindowsProxyServer.cs index b65599f..40dc5ec 100644 --- a/CitadelCore.Windows/Net/Proxy/WindowsProxyServer.cs +++ b/CitadelCore.Windows/Net/Proxy/WindowsProxyServer.cs @@ -5,30 +5,29 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -using System; -using System.Net; -using System.Net.Security; -using System.Security.Cryptography.X509Certificates; using CitadelCore.Diversion; using CitadelCore.Net.Proxy; using CitadelCore.Windows.Diversion; +using System; +using System.Net; namespace CitadelCore.Windows.Net.Proxy { /// - /// The WindowsProxyServer class implements the diversion functionality required for a functional transparent proxy server on the Windows platform. + /// The WindowsProxyServer class implements the diversion functionality required for a functional + /// transparent proxy server on the Windows platform. /// public class WindowsProxyServer : ProxyServer { /// - /// Creates a new WindowsProxyServer instance. Really there should only ever be a single instance - /// created at a time. + /// Creates a new WindowsProxyServer instance. Really there should only ever be a single + /// instance created at a time. /// /// /// The proxy server configuration to use. - /// + /// /// - /// Will throw if any one of the callbacks in the supplied configuration are not defined. + /// Will throw if any one of the callbacks in the supplied configuration are not defined. /// public WindowsProxyServer(ProxyServerConfiguration configuration) : base(configuration) { @@ -39,22 +38,22 @@ public WindowsProxyServer(ProxyServerConfiguration configuration) : base(configu /// Windows-specific diverter. /// /// - /// The endpoint where the proxy is listening for IPV4 HTTP connections. + /// The endpoint where the proxy is listening for IPV4 HTTP connections. /// /// - /// The endpoint where the proxy is listening for IPV4 HTTPS connections. + /// The endpoint where the proxy is listening for IPV4 HTTPS connections. /// /// - /// The endpoint where the proxy is listening for IPV6 HTTP connections. + /// The endpoint where the proxy is listening for IPV6 HTTP connections. /// /// - /// The endpoint where the proxy is listening for IPV6 HTTPS connections. + /// The endpoint where the proxy is listening for IPV6 HTTPS connections. /// /// - /// The platform specific diverter. + /// The platform specific diverter. /// protected override IDiverter CreateDiverter(IPEndPoint ipv4HttpEp, IPEndPoint ipv4HttpsEp, IPEndPoint ipv6HttpEp, IPEndPoint ipv6HttpsEp) - { + { return new WindowsDiverter((ushort)ipv4HttpEp.Port, (ushort)ipv4HttpsEp.Port, (ushort)ipv6HttpEp.Port, (ushort)ipv6HttpsEp.Port); } }