SIN@SAPPOROWORKSの覚書

C#を中心に、夜な夜な試行錯誤したコードの記録です。

TCPコネクションの一覧取得(プロセスID含む) (C#)(F#)


netstatに-oオプションを付けると、それぞれのコネクションのプロセスIDが表示されますが、GetTcpTable()では、プロセスIDを取得することができません。

WindowsXPまでは、この情報が必要な場合、非公開のAPIであるAllocateAndGetTcpExTableFromStack()を使用していましたが、Vista以降、同APIは廃止され、GetExtendedTcpTable()の使用が推奨されています。

GetExtendedTcpTableの定義は、下記のようになっているのですが、DllImportで呼び出すとき4番目と6番目の引数をint(32ビット)で指定しないと、エラー(※1 PInvokeStacImbalance)となりました。ドキュメントに誤りがあるのか、私の使用方法が間違っているのか、ちょっと不明です。
DWORD GetExtendedTcpTable(
 __out PVOID pTcpTable,
 __inout PDWORD pdwSize,
 __in BOOL bOrder,
 __in ULONG ulAf,
 __in TCP_TABLE_CLASS TableClass,
 __in ULONG Reserved
);

※1 PInvokeStacImbalanceが検出されました。
シグネチャが案マネージターゲットシグネチャに一致していないことが原因と考えられます。

サンプルは、「netstat -ao -p TCP」とした時と同じ出力です。

C#サンプル

using System;
using System.Runtime.InteropServices;

namespace Example {
    class Program {
        enum TCP_TABLE_CLASS {
            TCP_TABLE_BASIC_LISTENER,
            TCP_TABLE_BASIC_CONNECTIONS,
            TCP_TABLE_BASIC_ALL,
            TCP_TABLE_OWNER_PID_LISTENER,
            TCP_TABLE_OWNER_PID_CONNECTIONS,
            TCP_TABLE_OWNER_PID_ALL,
            TCP_TABLE_OWNER_MODULE_LISTENER,
            TCP_TABLE_OWNER_MODULE_CONNECTIONS,
            TCP_TABLE_OWNER_MODULE_ALL
        };
        
        [DllImport("iphlpapi.dll")]
        extern static int GetExtendedTcpTable(IntPtr pTcpTable, ref int pdwSize,
                    bool bOrder, uint ulAf,TCP_TABLE_CLASS TableClass, int Reserved);
        
        [StructLayout(LayoutKind.Sequential)]
        public struct MIB_TCPROW_OWNER_PID {
            public int State;
            public int LocalAddr;
            public int LocalPort;
            public int RemoteAddr;
            public int RemotePort;
            public int OwningPid;
        }

        public static string[] StateStrings = {
            "", "CLOSED","LISTENING","SYN_SENT","SYN_RCVD","ESTABLISHED","FIN_WAIT1",
            "FIN_WAIT2","CLOSE_WAIT","CLOSING","LAST_ACK","TIME_WAIT","DELETE_TCB"};

        static void Main(string[] args) {

            Console.WriteLine("プロトコルtローカルアドレスt外部アドレスt状態tPID");
            int size = 0;
            uint AF_INET = 2; // IPv4
            //必要サイズの取得            
            GetExtendedTcpTable(IntPtr.Zero,ref size,true,AF_INET,TCP_TABLE_CLASS.TCP_TABLE_OWNER_PID_ALL, 0);
            var p = Marshal.AllocHGlobal(size);//メモリ割当て
            //TCPテーブルの取得            
            if (GetExtendedTcpTable(p, ref size, true, AF_INET, TCP_TABLE_CLASS.TCP_TABLE_OWNER_PID_ALL, 0) == 0){
                var num = Marshal.ReadInt32(p);//MIB_TCPTABLE_OWNER_PID.dwNumEntries(データ数)
                var ptr = IntPtr.Add(p, 4);
                for (int i = 0; i < num; i++) {
                    var o = (MIB_TCPROW_OWNER_PID)Marshal.PtrToStructure(ptr, typeof(MIB_TCPROW_OWNER_PID));
                    if (o.RemoteAddr == 0)
                        o.RemotePort = 0;//RemoteAddrが0の場合は、RemotePortも0にする
                    Console.WriteLine(string.Format("TCPt{0}:{1}t{2}:{3}t{4}t{5}"
                        , ipstr(o.LocalAddr), htons(o.LocalPort)
                        , ipstr(o.RemoteAddr), htons(o.RemotePort)
                        , StateStrings[o.State]
                        , o.OwningPid
                        ));
                    ptr = IntPtr.Add(ptr, Marshal.SizeOf(typeof(MIB_TCPROW_OWNER_PID)));//次のデータ
                }
                Marshal.FreeHGlobal(p);  //メモリ開放
            }
            Console.WriteLine();
            Console.WriteLine("何かのキーを押してください。");
            Console.ReadKey();
        }

        private static string ipstr(int addr) {
            var b = BitConverter.GetBytes(addr);
            return string.Format("{0}.{1}.{2}.{3}", b[0], b[1], b[2], b[3]);
        }

        public static int htons(int i) {
            var tmp = (((0x000000ff & i) << 8) + ((0x0000ff00 & i) >> 8))
                       + ((0x00ff0000 & i) << 8) + ((0xff000000 & i) >> 8);
            return (int)tmp;
        }
    }
}

F#サンプル

open System
open System.Runtime.InteropServices

type TCP_TABLE_CLASS =
    | TCP_TABLE_BASIC_LISTENER=0
    | TCP_TABLE_BASIC_CONNECTIONS=1
    | TCP_TABLE_BASIC_ALL=2
    | TCP_TABLE_OWNER_PID_LISTENE=3
    | TCP_TABLE_OWNER_PID_CONNECTIONS=4
    | TCP_TABLE_OWNER_PID_ALL=5
    | TCP_TABLE_OWNER_MODULE_LISTENER=6
    | TCP_TABLE_OWNER_MODULE_CONNECTIONS=7
    | TCP_TABLE_OWNER_MODULE_ALL=8
        
[<DllImport("iphlpapi.dll")>]
extern int GetExtendedTcpTable(IntPtr pTcpTable,int *pdwSize, bool bOrder, UInt32 ulAf,
     TCP_TABLE_CLASS TableClass, int Reserved)
   
[<StructLayout(LayoutKind.Sequential)>]
type MIB_TCPROW_OWNER_PID =
    struct
        val State:int
        val LocalAddr:int
        val LocalPort:int
        val RemoteAddr:int
        val mutable RemotePort:int
        val OwningPid:int
    end

let StateStrings = [
            "";"CLOSED";"LISTENING";"SYN_SENT";"SYN_RCVD";"ESTABLISHED";"FIN_WAIT1";
            "FIN_WAIT2";"CLOSE_WAIT";"CLOSING";"LAST_ACK";"TIME_WAIT";"DELETE_TCB"]

let ipstr(addr:int)=
    let b = BitConverter.GetBytes(addr)
    sprintf "%d.%d.%d.%d" b.[0] b.[1] b.[2] b.[3] 
    
let htons(i) =  
    (((0x000000ff&&&i) <<< 8) + ((0x0000ff00&&&i) sma>>> 8)) + ((0x00ff0000&&&i)<<<8) + ((0xff000000&&&i)>>>8)

printfn "プロトコルtローカルアドレスt外部アドレスt状態tPID"
let AF_INET = 2u // IPv4
let mutable size = 0
//必要サイズの取得
GetExtendedTcpTable(IntPtr.Zero,&&size,true,AF_INET,TCP_TABLE_CLASS.TCP_TABLE_OWNER_PID_ALL, 0)|>ignore
let p = Marshal.AllocHGlobal(size);//メモリ割当て
//TCPテーブルの取得
if GetExtendedTcpTable(p, &&size, true, AF_INET, TCP_TABLE_CLASS.TCP_TABLE_OWNER_PID_ALL, 0) = 0 then
    let num = Marshal.ReadInt32(p);//MIB_TCPTABLE_OWNER_PID.dwNumEntries(データ数)
    let mutable ptr = IntPtr.Add(p, 4);
    for i in [0..num-1] do
        let mutable o = Marshal.PtrToStructure(ptr, typeof<MIB_TCPROW_OWNER_PID>):?>MIB_TCPROW_OWNER_PID
        if o.RemoteAddr = 0 then o.RemotePort <- 0//RemoteAddrが0の場合は、RemotePortも0にする
        printfn "TCPt%s:%dt%s:%dt%st%d"
            (ipstr(o.LocalAddr)) (htons(o.LocalPort))
            (ipstr(o.RemoteAddr)) (htons(o.RemotePort))
            StateStrings.[o.State] o.OwningPid
        ptr <- IntPtr.Add(ptr, Marshal.SizeOf(typeof<MIB_TCPROW_OWNER_PID>));//次のデータ
    Marshal.FreeHGlobal(p);  //メモリ開放
    
printfn "何かのキーを押してください。"
Console.ReadKey() |> ignore