sai’s diary

プログラミング関連の備忘録とつぶやきを書いています。

Windows用のloopbackアドレス簡易パケットキャプチャ

WindowsだとWiresharkでloopbackアドレス宛の通信がキャプチャ出来ない、ということでrawソケットを利用した簡易版pcapファイル作成プログラムを作成してみました。

C#でもunsafeを利用するとポインタが扱える、 pcapファイルは意外とフォーマットが単純だった の2点が勉強になりました。

C#なのでポインタ使用箇所はBitConverter.GetBytesメソッドを使った方が良いと思いますが覚えたてのunsafeを使ってみたく。。。

バグ・改善点等、お気づきの点がありましたらコメント頂ければ幸いです。

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;

namespace rawPcap
{
    class Program
    {
        // https://wiki.wireshark.org/Development/LibpcapFileFormat
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        struct PcapGlobalHeader
        {
            public UInt32 magicNumver;  // マジックナンバー
            public UInt16 versionMajor; // メジャーバージョン番号
            public UInt16 versionMinor; // マイナーバージョン番号
            public Int32 thiszone;      // タイムスタンプの補正
            public UInt32 sigfigs;      // タイムスタンプの精度
            public UInt32 snapLen;      // キャプチャしたパケットの最大数(オクテット単位)
            public UInt32 network;      // データリンク種別(http://www.tcpdump.org/linktypes.html)
        }
        unsafe private static readonly int PcapGlobalHeaderSize = sizeof(PcapGlobalHeader);

        private static readonly UInt32 LinkTypeRaw = 101;
        private static readonly UInt32 MagicNumver = 0xa1b2c3d4;
        private static readonly UInt32 snapLenDefault = 65535;
        private static readonly UInt16 Major = 2;
        private static readonly UInt16 Minor = 4;

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        struct PcapRecordHeader
        {
            public UInt32 tsSec;        // タイムスタンプ(秒)
            public UInt32 tsUsec;       // タイムスタンプ(マイクロ秒)
            public UInt32 inclLen;      // number of octets of packet saved in file
            public UInt32 origLen;      // actual length of packet
        }
        unsafe private static readonly int PcapRecordHeaderSize = sizeof(PcapRecordHeader);

        private static readonly ulong maxFileCount = 65535;

        static void Main(string[] args)
        {
            Socket sock = null;
            FileStream stream = null;

            try
            {
                ulong count;

                Console.CursorVisible = false;

                Console.CancelKeyPress += delegate
                {
                    if (null != sock) { sock.Close(); sock = null; }
                    if (null != stream) { stream.Close(); stream = null; }
                };

                StringBuilder fileName = new StringBuilder();

                sock = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP);
                sock.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.HeaderIncluded, true);
                sock.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 0));

                sock.IOControl(IOControlCode.ReceiveAll, BitConverter.GetBytes(1), null);

                byte[] recvBuffer = new byte[sock.ReceiveBufferSize];

                for (count = 0; count < maxFileCount; count++)
                {
                    fileName.Clear();
                    fileName.AppendFormat("rawPcap{0:D5}.pcap", count);
                    if (!File.Exists(fileName.ToString()))
                    {
                        break;
                    }
                }

                if (maxFileCount == count)
                {
                    Console.WriteLine("自動生成するキャプチャファイルが上限に達しました。({0})", fileName.ToString());
                    return;
                }

                stream = new FileStream(fileName.ToString(), FileMode.CreateNew);
                byte[] globalHeader = new byte[PcapGlobalHeaderSize];
                byte[] recordHeader = new byte[PcapRecordHeaderSize];
                unsafe
                {
                    fixed (byte* bytePointer = globalHeader)
                    {
                        PcapGlobalHeader* globalHeaderPointer = (PcapGlobalHeader*)bytePointer;
                        globalHeaderPointer->magicNumver = MagicNumver;
                        globalHeaderPointer->versionMajor = Major;
                        globalHeaderPointer->versionMinor = Minor;
                        globalHeaderPointer->thiszone = 0;
                        globalHeaderPointer->sigfigs = 0;
                        globalHeaderPointer->snapLen = snapLenDefault;
                        globalHeaderPointer->network = LinkTypeRaw;
                    }
                }

                stream.Write(globalHeader, 0, globalHeader.Length);

                count = 0;
                int recvSize;
                while (true)
                {
                    Console.Write("recv packet={0}                        ", count);
                    recvSize = sock.Receive(recvBuffer);
                    TimeSpan span = DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1, 0, 0, 0));
                    unsafe
                    {
                        fixed (byte* bytePointer = recordHeader)
                        {
                            PcapRecordHeader* recordHeaderPointer = (PcapRecordHeader*)bytePointer;
                            recordHeaderPointer->tsSec = (UInt32)span.TotalSeconds;
                            recordHeaderPointer->tsUsec = (UInt32)span.Milliseconds*1000;
                            recordHeaderPointer->inclLen = (UInt32)recvSize;
                            recordHeaderPointer->origLen = (UInt32)recvSize;
                        }
                    }
                    stream.Write(recordHeader, 0, recordHeader.Length);
                    stream.Write(recvBuffer, 0, recvSize);
                    stream.Flush();

                    Console.CursorLeft = 0;
                    count++;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("{0}", e);
            }
            finally
            {
                if (null != sock) { sock.Close(); sock = null; }
                if (null != stream) { stream.Close(); stream = null; }
            }

            return;
        }
    }
}