[BlueLeaf1336]> PROBLEMS> WinSock>

WinSock - デバイスドライバを使用しないパケットモニタ

historyTOP

2004/05/02:作成
2004/05/05:スレッド停止時に shutdown() 関数で送受信処理を中断するように修正。ログ出力方法を変更。従来の簡略ダンプに加えてフルダンプを可能に。
2004/05/06:このプログラムでパケットをモニタするとパケットおかしくなるような...

referenceTOP

commentTOP

まあ、動いてます。それだけです。本当に大丈夫か? と思ってます。WSARecv とか Delphi のサンプルがどこにもない!

WinSock2.2 からこのような処理が可能になったということで、WinSock2.pas をもらってきてます。これが再配布可能かどうかは疑問ですが多分大丈夫かと。オリジナルの圧縮ファイルに含まれてたすべてのファイルを一切触らずに同梱してますし。

今回もお約束、信じないように。(もちろん参考にさせていただいたサイトのことではありません。Delphiに翻訳したこのページの以下のコードを、です。)

(2004/05/05)基本的な処理は全然変わってないので、HTML化したコードは以前のままですが、ダウンロード用のコードは修正しています。ただし以前のコード(HTML化したものに対応するもの)もダウンロードできるようにしています。
これを使って、完敗した ICMP ページのリベンジに役立てたいというか...。

codeTOP

unit Main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, WinSock2;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Button1: TButton;
    ComboBox1: TComboBox;
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private

  public

    wsd: TWSAData;
    sock: TSocket;
    hThread: TThread;
    ThreadId: DWORD;

    function    ListAdapter(): Boolean;
    function    InitAdapter(): Boolean;
    procedure   Print(const buf: array of Byte; const len: integer);

  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

const
    Protocol: array[0..99] of string =
    (
    'Reserved',     //  0
    'ICMP',         //  1
    'IGMP',         //  2
    'GGP',          //  3
    'IP',           //  4
    'ST',           //  5
    'TCP',          //  6
    'UCL',          //  7
    'EGP',          //  8
    'IGP',          //  9
    'BBN-RCC-MON',  // 10
    'NVP-II',       // 11
    'PUP',          // 12
    'ARGUS',        // 13
    'EMCON',        // 14
    'XNET',         // 15
    'CHAOS',        // 16
    'UDP',          // 17
    'MUX',          // 18
    'DCN-MEAS',     // 19
    'HMP',          // 20
    'PRM',          // 21
    'XNS-IDP',      // 22
    'TRUNK-1',      // 23
    'TRUNK-2',      // 24
    'LEAF-1',       // 25
    'LEAF-2',       // 26
    'RDP',          // 27
    'IRTP',         // 28
    'ISO-TP4',      // 29
    'NETBLT',       // 30
    'MFE-NSP',      // 31
    'MERIT-INP',    // 32
    'SEP',          // 33
    '3PC',          // 34
    'IDPR',         // 35
    'XTP',          // 36
    'DDP',          // 37
    'IDPR-CMTP',    // 38
    'TP++',         // 39
    'IL',           // 40
    'SIP',          // 41
    'SDRP',         // 42
    'SIP-SR',       // 43
    'SIP-FRAG',     // 44
    'IDRP',         // 45
    'RSVP',         // 46
    'GRE',          // 47
    'MHRP',         // 48
    'BNA',          // 49
    'SIPP-ESP',     // 50
    'SIPP-AH',      // 51
    'I-NLSP',       // 52
    'SWIPE',        // 53
    'NHRP',         // 54
    'unknown',      // 55
    'unknown',      // 56
    'unknown',      // 57
    'unknown',      // 58
    'unknown',      // 59
    'unknown',      // 60
    'unknown',      // 61
    'CFTP',         // 62
    'unknown',      // 63
    'SAT-EXPAK',    // 64
    'KRYPTOLAN',    // 65
    'RVD',          // 66
    'IPPC',         // 67
    'unknown',      // 68
    'SAT-MON',      // 69
    'VISA',         // 70
    'IPCV',         // 71
    'CPNX',         // 72
    'CPHB',         // 73
    'WSN',          // 74
    'PVP',          // 75
    'BR-SAT-MON',   // 76
    'SUN-ND',       // 77
    'WB-MON',       // 78
    'WB-EXPAK',     // 79
    'ISO-IP',       // 80
    'VMTP',         // 81
    'SECURE-VMTP',  // 82
    'VINES',        // 83
    'TTP',          // 84
    'NSFNET-IGP',   // 85
    'DGP',          // 86
    'TCF',          // 87
    'IGRP',         // 88
    'OSPFIGP',      // 89
    'Sprite-RPC',   // 90
    'LARP',         // 91
    'MTP',          // 92
    'AX.25',        // 93
    'IPIP',         // 94
    'MICP',         // 95
    'SCC-SP',       // 96
    'ETHERIP',      // 97
    'ENCAP',        // 98
//    'unknown',      // 98(?)
    'GMTP'          // 99
    );

    SIO_RCVALL  = $98000001;
    MAX_IP_SIZE = 65535;
    LIST_SIZE = 4096;

//  ---------------------------------------------------------------------------
//  アダプタ一覧
function    TForm1.ListAdapter(): Boolean;
var
    Temp: string;
    slist: LPSOCKET_ADDRESS_LIST;
    d: DWORD;
    s: TSocket;
    i, ret: integer;
begin
    ComboBox1.Clear;
    Result := false;

    //  ソケット作成
    s := WSASocket(AF_INET, SOCK_RAW, IPPROTO_IP, nil, 0, WSA_FLAG_OVERLAPPED);

    //  失敗
    if (INVALID_SOCKET = s) then
    begin
        Temp := Format('WSASocket(AF_INET,SOCK_RAW) failed', []);
        Memo1.Lines.Add(Temp);
    end
    //  成功
    else
    begin
        //  メモリ確保
        GetMem(slist, LIST_SIZE);

        try
            //  WSAIoctl失敗
            ret := WSAIoctl(s, SIO_ADDRESS_LIST_QUERY, nil, 0, slist, LIST_SIZE, @d, nil, nil);
            if (SOCKET_ERROR = ret) then
            begin
                Temp := Format('WSAIoctl(SIO_ADDRESS_LIST_QUERY) faild.', []);
                Memo1.Lines.Add(Temp);
            end
            //  成功
            else
            begin
                //  列挙
                for i := 0 to slist.iAddressCount - 1 do
                begin
                    Temp := Format('%d. (%s)', [i, inet_ntoa((slist.Address[i].lpSockaddr).sin_addr)]);
                    ComboBox1.Items.Add(Temp);
                end;
                //  ここまで来たら
                Result := true;
            end;

        finally
            //  メモリ解放
            FreeMem(slist, LIST_SIZE);
            //  ソケット破棄
            closesocket(s);
        end;
    end;

    if (ComboBox1.Items.Count > 0) then ComboBox1.ItemIndex := 0;
end;

//  ---------------------------------------------------------------------------
//  アダプタの初期化
function    TForm1.InitAdapter(): Boolean;
var
    Temp: string;
    ret: integer;
    optval: integer;
    d: DWORD;
    slist: LPSOCKET_ADDRESS_LIST;
    addr_in: TSockAddrIn;
begin
    Result := false;

    //  SOCKETの生成
    sock := WSASocket(AF_INET, SOCK_RAW, IPPROTO_IP, nil, 0, WSA_FLAG_OVERLAPPED);
    if (INVALID_SOCKET = sock) then
    begin
        Temp := Format('WSASocket(AF_INET,SOCK_RAW) failed', []);
        Memo1.Lines.Add(Temp);
    end
    else
    begin
        //  メモリ確保
        GetMem(slist, LIST_SIZE);

        try
            //  アダプタのリスト取得
            ret := WSAIoctl(sock, SIO_ADDRESS_LIST_QUERY, nil, 0, slist, LIST_SIZE, @d, nil, nil);
            if (SOCKET_ERROR = ret) then
            begin
                Temp := Format('WSAIoctl(SIO_ADDRESS_LIST_QUERY) faild.', []);
                Memo1.Lines.Add(Temp);
            end
            else
            begin
                //  SOCKET のデータセット
                FillChar(addr_in, SizeOf(addr_in), 0);
                addr_in.sin_addr.S_addr := slist.Address[ComboBox1.ItemIndex].lpSockaddr.sin_addr.S_addr;
                addr_in.sin_family := AF_INET;
                addr_in.sin_port := htons(0);
                //  bind
                ret := bind(sock, @addr_in, SizeOf(addr_in));
                if (SOCKET_ERROR = ret) then
                begin
                    Temp := Format('bind failed', []);
                    Memo1.Lines.Add(Temp);
                end
                else
                begin
                    optval := 1;
                    ret := WSAIoctl(sock, SIO_RCVALL, @optval, sizeof(optval), nil, 0, @d, nil, nil);
                    if (SOCKET_ERROR = ret) then
                    begin
                        Temp := Format('WSAIoCtl(SIO_RCVALL) failed', []);
                        Memo1.Lines.Add(Temp);
                    end
                    else
                    begin
                        Result := true;
                    end;
                end;
            end;

        finally
            //  メモリ解放
            FreeMem(slist, LIST_SIZE);
        end;
    end;
end;

//  ---------------------------------------------------------------------------
//  ダンプ
procedure   TForm1.Print(const buf: array of Byte; const len: integer);

    //  c's isprint
    function    IsPrint(const c: Char): Boolean;
    begin
        Result := (c in [#$20..#$7E]);
    end;

var
    t: SYSTEMTIME;
    Tmp: string;
    i, j: integer;
    d: integer;
    c: char;
begin
    GetLocalTime(t);
    Tmp := '-----------------------------------------------------------';
    Memo1.Lines.Add(Tmp);

    //  時刻
    Tmp := Format('%2.2d/%2.2d/%2.2d %2.2d:%2.2d:%2.2d Protocol: %s',
                  [t.wYear, t.wMonth, t.wDay, t.wHour, t.wMinute, t.wSecond, Protocol[buf[9]]]);
    Memo1.Lines.Add(Tmp);

    //  送信元アドレス
    Tmp := Format('Source Address: %d.%d.%d.%d',
                  [buf[12], buf[13], buf[14], buf[15]]);
    Memo1.Lines.Add(Tmp);

    //  送信先アドレス
    Tmp := Format('Destination Address: %d.%d.%d.%d',
                  [buf[16], buf[17], buf[18], buf[19]]);
    Memo1.Lines.Add(Tmp);

    //  ポート
    Tmp := Format('Source Port: %d  Destination Port: %d',
                  [buf[20] * 256 + buf[21], buf[22] * 256 + buf[23]]);
    Memo1.Lines.Add(Tmp);

    Memo1.Lines.Add('-- dump --');

    //  16進ダンプ
    for i := 0 to 3 - 1 do
    begin
        d := len - i * 16;
        if (d > 16) then d := 16;
        if (d <= 0) then break;

        //  バイナリ表示
        Tmp := Format('%8.8x : ', [i]);

        for j := 0 to d - 1 do
        begin
            Tmp := Tmp + Format('%2.2x ', [buf[i * 16 + j]]);
        end;

        for j := d to 16 - 1 do Tmp := Tmp + '   ';

        //  文字列表示
        for j := 0 to d - 1 do
        begin
            c := Chr(buf[i * 16 + j]);
            if not IsPrint(c) then c := '.';
            Tmp := Tmp + Format('%s ', [c]);
        end;

        Memo1.Lines.Add(Tmp);

        if (d <> 16) then break;
    end;
end;

//  ---------------------------------------------------------------------------
//  コンストラクタ
procedure TForm1.FormCreate(Sender: TObject);
begin
    //  Ver2.2 でWinSockを初期化する
    WSAStartup(MAKEWORD(2, 2), wsd);
    hThread := nil;
    //アダプタのリストをコンボボックに取得
    ListAdapter();
end;

//  ---------------------------------------------------------------------------
//  デストラクタ
procedure TForm1.FormDestroy(Sender: TObject);
begin
    //  キャプチャスレッドが走行中の場合ストップする
    if (hThread <> nil) then
    begin
        hThread.Terminate;
        hThread.Free;
        hThread := nil;
    end;
    //  WinSockの終了処理
    WSACleanup();
end;

//  ---------------------------------------------------------------------------
//  キャプチャスレッド
type
    TCaputureThread = class(TThread)
    private
        Msg: string;
        Buf: array[0..MAX_IP_SIZE - 1] of Byte;
        Len: Cardinal;
        procedure   DoError();
        procedure   DoPrint();
    protected
        procedure Execute; override;
    end;

//  ---------------------------------------------------------------------------
procedure   TCaputureThread.DoError();
begin
    Form1.Memo1.Lines.Add(Msg);
end;

//  ---------------------------------------------------------------------------
procedure   TCaputureThread.DoPrint();
begin
    Form1.Print(Buf, Len);
end;

//  ---------------------------------------------------------------------------
//  キャプチャスレッド本体
procedure TCaputureThread.Execute;
var
    wsb: PWSABUF;
    ret: integer;
    Flags: Cardinal;
begin
    //  メモリ確保
    New(wsb);
    wsb.buf := @Buf;
    wsb.len := MAX_IP_SIZE;

    while not Terminated do
    begin
        Sleep(1);
        Application.ProcessMessages();

        FillChar(Buf, SizeOf(Buf), 0);
        Flags := 0;
        //  パケット取得
        ret := WSARecv(Form1.sock, wsb, 1, Len, Flags, nil, nil);
        if (SOCKET_ERROR = ret) then
        begin
            Msg := Format('WSARecv failed. Code %d', [WSAGetLastError(), Form1.sock]);
            if not Terminated then Synchronize(DoError);
            continue;
        end;
        //  パケット表示
        if not Terminated then Synchronize(DoPrint);
    end;

    //  メモリ解放
    Dispose(wsb);
end;

//  ---------------------------------------------------------------------------
//  キャプチャ開始・終了(キャプチャスレッドの生成・破棄)
procedure TForm1.Button1Click(Sender: TObject);
begin
    //  キャプチャ開始処理
    if (hThread = nil) then
    begin
        if (InitAdapter()) then
        begin
            //  キャプチャスレッド生成
            hThread := TCaputureThread.Create(true);
            hThread.Resume;
            Button1.Caption := 'Stop';
        end;
    end
    //  キャプチャ終了処理
    else
    begin
        //  ひとこと
        ShowMessage('Stop に切り替わらない場合は、WSARecv で処理がとまってます'
            + #13#10'何か通信すればとまります'
            + #13#10'ブラウザを開いてどこかのサイトを見るとか...');
        //  キャプチャスレッドの破棄
        hThread.Terminate;
        hThread.Free;
        hThread := nil;
        Button1.Caption := 'Start';
    end;
end;

end.

screenshot & downloadTOP

ソースダウンロード
20040502PacketDump.zip(33,019bytes)
こちらは記念に保存。

ソースダウンロード
20040505PacketDump.zip(205,143bytes)

EOFTOP