SIN@SAPPOROWORKSの覚書

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

IcmpSendEcho()によるpingの送受信(#C)(#F)


IPHLPAPI.DLLのIcmpSendEcho()によりpingの送受信が行えます。
以前に紹介した、System.Net.NetworkInformation.Pingより、ややきめ細かい指定が可能です。
C#サンプル

using System;
using System.Runtime.InteropServices;

namespace Example {
    class Program {

        [DllImport("icmp.dll", SetLastError=true)]
        static extern IntPtr IcmpCreateFile();
        [DllImport("icmp.dll", SetLastError=true)]
        static extern bool IcmpCloseHandle(IntPtr handle);
        [DllImport("icmp.dll", SetLastError=true)]
        static extern Int32 IcmpSendEcho(IntPtr icmpHandle, Int32 destinationAddress, String requestData,
             Int16 requestSize, ref ICMP_OPTIONS requestOptions, ref ICMP_ECHO_REPLY replyBuffer,
             Int32 replySize, Int32 timeout);
        [DllImport("ws2_32.dll", ExactSpelling = true)]
        static extern Int32 inet_addr(string cp);

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        private struct ICMP_OPTIONS {
            public Byte Ttl;
            public Byte Tos;
            public Byte Flags;
            public Byte OptionsSize;
            public IntPtr OptionsData;
        };

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        private struct ICMP_ECHO_REPLY {
            public Int32 Address;
            public Int32 Status;
            public Int32 RoundTripTime;
            public Int16 DataSize;
            public Int16 Reserved;
            public IntPtr DataPtr;
            public ICMP_OPTIONS Options;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 250)]
            public String Data;
        }

        static void Main(string[] args){
            ICMP_ECHO_REPLY reply = new ICMP_ECHO_REPLY();
            ICMP_OPTIONS option = new ICMP_OPTIONS();
            option.Ttl = 255;
            var data = "ABCDEF";
            var timeout = 1000;
            var ipStr = "192.168.0.254";

            var handle = IcmpCreateFile();
            if (0 != IcmpSendEcho(handle, inet_addr(ipStr), data, (Int16)data.Length
                    , ref option, ref reply, Marshal.SizeOf(reply), timeout)) {
                if (reply.Status == 0) {
                    Console.WriteLine(string.Format("{0} からの応答: バイト数 ={1} 時間 {2}ms TTL={3}"
                              , ipStr, reply.DataSize, reply.RoundTripTime, reply.Options.Ttl));
                } else {
                    Console.WriteLine("ERROR Status={0}",reply.Status);
                }
            } else {
                Console.WriteLine("ERROR Status={0}", reply.Status);
            }
            IcmpCloseHandle(handle);
            Console.WriteLine("n何かのキーを押してください。");
            Console.ReadKey();
        }
    }
}

F#サンプル

やはり、下記のように定義することはできません。
[<DllImport("icmp.dll")>]
extern Int32 IcmpSendEcho( , , ICMP_ECHO_REPLY *replay , , , )

ジェネリックコンストラクターの型'ICMP_ECHO_REPLY'はアンマネージ型にする必要があります。
構造体の中が要素がすべてアンマネージ型の場合は、上記のような指定が可能なのですがICMP_ECHO_REPLYにはstringが含まれているためエラーとなるようです。

ここに構造体のポインタで使用できないため、いちいちMarshalでIntPtrへ変換する手順が非常にうるさくなってしまいます。解決策では無いのですが、今回は、ラッパ関数_IcmpSendEcho()を定義してみました。



#nowarn "9" "51"

open System
open System.Runtime.InteropServices

[&lt;Struct;StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)>]
type ICMP_OPTIONS =
    val mutable Ttl:SByte
    val Tos:Byte
    val Flags:Byte
    val OptionsSize:Byte
    val OptionsData:IntPtr

[&lt;Struct;StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)>]
type ICMP_ECHO_REPLY =  
    val Address:IntPtr
    val Status:Int32
    val RoundTripTime:Int32
    val DataSize:Int16
    val Reserved:Int16
    val DataPtr:IntPtr
    val Options:ICMP_OPTIONS
    [&lt;MarshalAs(UnmanagedType.ByValTStr, SizeConst = 250)>]
    val Data:String

[&lt;DllImport("icmp.dll", SetLastError=true)>]
extern IntPtr IcmpCreateFile()
[&lt;DllImport("icmp.dll", SetLastError=true)>]
extern bool IcmpCloseHandle(IntPtr handle)
[&lt;DllImport("icmp.dll", SetLastError=true)>]
extern Int32 IcmpSendEcho(IntPtr icmpHandle, Int32 destinationAddress, String requestData,
               Int16 requestSize,ICMP_OPTIONS *requestOptions,IntPtr replay,
               Int32 replySize, Int32 timeout)
[&lt;DllImport("ws2_32.dll", ExactSpelling = true)>]
extern Int32 inet_addr(string cp)

//IcmpSendEcho()をラッピング
let _IcmpSendEcho(handle,addr,data,ttl,timeout) =
    let TYPE = typeof&lt;ICMP_ECHO_REPLY>
    let mutable option = new ICMP_OPTIONS()
    option.Ttl &lt;- ttl
    let size = Marshal.SizeOf(TYPE)
    let p = Marshal.AllocCoTaskMem(size)
    let result = IcmpSendEcho(handle,addr,data,int16(data.Length),&&option,p,size,timeout);
    let reply = Marshal.PtrToStructure(p,TYPE):?>ICMP_ECHO_REPLY
    Marshal.FreeCoTaskMem(p)
    result,reply

//main()
let data = "ABCDEF" //データ
let timeout = 1000 //タイムアウト
let ttl = 125y //TTL
let ipStr = "192.168.0.254" //宛先
let handle = IcmpCreateFile() 
let result,reply = _IcmpSendEcho(handle,inet_addr(ipStr),data,ttl,timeout)
if result &lt;> 0 then
    if reply.Status = 0 then
        printfn "%s からの応答: バイト数 =%d 時間 %dms TTL=%d" 
       ipStr reply.DataSize reply.RoundTripTime reply.Options.Ttl
    else
        printfn "ERROR Status=%d" reply.Status
else
        printfn "ERROR Status=%d" reply.Status

IcmpCloseHandle(handle)|>ignore
printfn "n何かのキーを押してください。"
Console.ReadKey() |> ignore