Search code examples
windowssocketswinapi

How do I close a socket (ipv4 and ipv6) connection on Windows from any process?


How do I close tcp v4 and tcp v6 connections on Windows? I don't want to kill the entire process that has the open connection as this obviously will kick everyone else off that process. I need to do this from a separate process, and so will not have access to socket handles, etc. I am using Windows API to get tcp table, etc. so I know which connections are active.


Solution

  • UPDATE 2022-05-01, found this gem at https://www.x86matthew.com/view_post?id=settcpentry6

    UPDATE 2024-04-21, I got the C# PInvoke code working for ipv4 and ipv6. Getting everything in the right byte order was key.

    Full .NET 8 code, tested through Windows Vista to 11:

    using System;
    using System.Net;
    using System.Net.Sockets;
    using System.Runtime.InteropServices;
    
    // add 2.0.0 version as nuget package
    using DigitalRuby.IPBanCore;
    
    namespace YourNamespace;
    
    /// <summary>
    /// Socket closer interface
    /// </summary>
    public interface ISocketCloser
    {
        /// <summary>
        /// Close a socket using low level windows API. Handles ipv4 and ipv6.
        /// </summary>
        /// <param name="local">Local end point</param>
        /// <param name="remote">Remote end point</param>
        /// <returns>True if closed, false if not</returns>
        bool CloseSocket(IPEndPoint local, IPEndPoint remote);
    }
    
    /// <summary>
    /// Close sockets on Windows or Linux
    /// </summary>
    public partial class SocketCloser : ISocketCloser
    {
        private const int MIB_TCP_STATE_DELETE_TCB = 12;
    
        private static readonly byte[] moduleId = [0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x4A, 0x00, 0xEB, 0x1A, 0x9B, 0xD4, 0x11, 0x91, 0x23, 0x00, 0x50, 0x04, 0x77, 0x59, 0xBC];
        private static readonly IntPtr moduleIdPtr;
    
        [LibraryImport("iphlpapi.dll", SetLastError = true)]
        private static partial uint SetTcpEntry(ref MIB_TCPROW pTcpRow);
    
        [LibraryImport("nsi.dll", SetLastError = true)]
        private static partial uint NsiSetAllParameters(uint action, uint flags, IntPtr moduleId, uint operation, IntPtr buffer, uint bufferLength, IntPtr metric, uint metricLength);
    
        [StructLayout(LayoutKind.Sequential)]
        private struct MIB_TCPROW
        {
            public uint dwState;
            public uint dwLocalAddr;
            public uint dwLocalPort;
            public uint dwRemoteAddr;
            public uint dwRemotePort;
        }
    
        [StructLayout(LayoutKind.Sequential)]
        private struct KillTcpSocketData_V6
        {
            public ushort wLocalAddressFamily;
            public ushort wLocalPort;
            public uint bReserved1;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
            public byte[] bLocal;
            public uint dwLocalScopeID;
    
            public ushort wRemoteAddressFamily;
            public ushort wRemotePort;
            public uint bReserved2;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
            public byte[] bRemote;
            public uint dwRemoteScopeID;
        };
    
        static SocketCloser()
        {
            moduleIdPtr = Marshal.AllocHGlobal(moduleId.Length);
            Marshal.Copy(moduleId, 0, moduleIdPtr, moduleId.Length);
        }
    
        /// <inheritdoc />
        public bool CloseSocket(IPEndPoint local, IPEndPoint remote)
        {
            if (OSUtility.IsLinux)
            {
                return CloseSocketLinux(local, remote);
            }
            else if (OSUtility.IsWindows)
            {
                return CloseSocketWindows(local, remote);
            }
    
            return false;
        }
    
        private static bool CloseSocketLinux(IPEndPoint local, IPEndPoint remote)
        {
            // sudo ss --kill state all src IP_ADDRESS:PORT dst IP_ADDRESS:PORT
            string command = $"ss --kill state all src \"{local.Address}:{local.Port}\" dst \"{remote.Address}:{remote.Port}\"";
            OSUtility.StartProcessAndWait("sudo", command, 0);
            return true;
        }
        private static bool CloseSocketWindows(IPEndPoint local, IPEndPoint remote)
        {
            var localPortFixed = (ushort)IPAddress.HostToNetworkOrder((short)local.Port);
            var remotePortFixed = (ushort)IPAddress.HostToNetworkOrder((short)remote.Port);
    
            if (local.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
            {
                MIB_TCPROW row = new()
                {
                    dwState = MIB_TCP_STATE_DELETE_TCB,
                    dwLocalAddr = local.Address.ToUInt32(false),
                    dwLocalPort = (uint)localPortFixed,
                    dwRemoteAddr = remote.Address.ToUInt32(false),
                    dwRemotePort = (uint)remotePortFixed
                };
                var result = SetTcpEntry(ref row);
                return result == 0 || result == 317;
            }
            else if (local.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)
            {
                KillTcpSocketData_V6 row6 = new()
                {
                    wLocalAddressFamily = (ushort)AddressFamily.InterNetworkV6,
                    wLocalPort = localPortFixed,
                    bLocal = local.Address.GetAddressBytes(),
                    bRemote = remote.Address.GetAddressBytes(),
                    bReserved1 = 0,
                    bReserved2 = 0,
                    dwLocalScopeID = (uint)IPAddress.HostToNetworkOrder(local.Address.ScopeId),
                    dwRemoteScopeID = (uint)IPAddress.HostToNetworkOrder(remote.Address.ScopeId),
                    wRemoteAddressFamily = (ushort)AddressFamily.InterNetworkV6,
                    wRemotePort = remotePortFixed
                };
    
                // Assume global module ID and other parameters are correctly set
                var ptrSize = Marshal.SizeOf<KillTcpSocketData_V6>();
                var ptr = Marshal.AllocHGlobal(ptrSize);
                try
                {
                    // Call the undocumented API (the values for module ID, etc., must be correct)
                    Marshal.StructureToPtr(row6, ptr, false);
                    var result = NsiSetAllParameters(1, 2, moduleIdPtr, 16, ptr, (uint)ptrSize, IntPtr.Zero, 0);
                    return result == 0 || result == 317;
                }
                finally
                {
                    // Cleanup
                    Marshal.FreeHGlobal(ptr);
                }
            }
    
            return false;
        }
    }