続々・なかなか無い

前述の通り、Windowsというか、Visual Studio 2008 Express Editionを触る羽目になったのだけど、

とにかく面倒。


いや、Cそのものもかなり面倒に思うのだけど。。。




とにかく、ARP送信とARP受信が出来ればいいという事もあるし、スレッドは別の方法を採るので、あくまでシングルスレッドの中でARPを送信しつつ受信する事を考えてみた、、、こんな感じで。
※そもそもセグメント全体をARPでスキャンする事を考えているので、マルチスレッド化は必須。

//---------------------------------------------------------------------------
BOOL Scanning(char NameList[MAX_ADAPTER][1024], unsigned long AddressList[MAX_ADAPTER], int Index, unsigned long ipaddr_dst)
{
    unsigned char Buffer[MAX_RECV_SIZE];
    unsigned long Len;
    unsigned int Count = 0;
    DWORD sTime, eTime, dTime;

    // キャプチャの開始
    BOOL Ret;
    if((Ret = Start(NameList[Index])) == FALSE) {
        printf("can't open Network Interface %s.¥n", &NameList[Index]);
        return FALSE;
    }

    sTime = timeGetTime();
    eTime = sTime + RECV_TIMER;        // スキャン時間
    dTime = sTime + SEND_WAIT;        // ARP送信タイマー

    // キャプチャ中
    while(sTime < eTime){
            sTime = timeGetTime();
            if(dTime < sTime) {
                ++Count;
                if (Count >= SEND_COUNT) {
                    dTime = sTime + RECV_TIMER; // ARP送信タイマー(再送しない)
                } else {
                    dTime = sTime + SEND_TIMER; // ARP送信タイマー(再送する)
                }
                // ARP要求を送信
                int SendLen;
                SendLen = SendArp(AddressList[Index], ipaddr_dst, NameList[Index]);
                if(SendLen < 0) {
                    printf("error: send packet %d¥n", SendLen);
                }
            }

        if(Max>=MAX){
            printf("%dパケットまでしか取得することができません。キャプチャを終了します。¥n", MAX);
            continue;
        }
        Sleep(0); // CPU100%使用を避ける

         //パケット受信
        if(Recv(Buffer,&Len)){

            if(Len!=0){
                // パケットの保存
                Data[Max].buf = new unsigned char[Len];
                memcpy(Data[Max].buf,Buffer,Len);
                Data[Max].len = Len;
                Max++;

                // パケットの表示
                RecvArp(Buffer,Len,ipaddr_dst);
            }else{
                // 受信パケットが無い場合は、少し休む
                Sleep(1);
            }
        }else{
            printf("パケット受信に失敗しました。ERROR=%d",GetLastError());
        }
    }
    // ソケットのクローズ
    if(!Stop()){
        printf("既にソケットがクローズしています。");
        return FALSE;
    }
    return TRUE;
}
RECV_TIMERで設定した時間が来るまではひたすら受信しながら回り続ける。
SEND_WAITで設定した時間が来たら、SEND_COUNTで設定した回数だけSEND_TIMERで設定した時間間隔を空けて、ARPを送信する・・・という。
RECV_TIMERに1000ms、SEND_WAITに10ms、SEND_TIMERに100ms、SEND_COUNTに3回を指定した場合、時間経過で言えば
起動→10ms経過でARP送信→110ms経過でARP送信→210ms経過でARP送信→→→1000ms経過で停止
という動作になる。

WinPcapを使った場合、ネットワークからの受信に関してはほぼファイル操作時と似た操作になる。
pcap_open_live()でネットワークデバイスをオープンして得られたファイルポインタに対して操作を行う。
BOOL Start(char *AdapterName)
{
    // WPCAPオブジェクトが使用中の場合は、処理を継続できない
    if(fp!=NULL){
        SetLastError(ERROR_WPCAP_BUSY);
        return FALSE;
    }

    int timeout = 20;
    if((fp=pcap_open_live(AdapterName,MAX_RECV_SIZE,0/*非プロミスキャスモード*/,timeout,ErrorBuf))== NULL){
        SetLastError(ERROR_OPEN_LIVE);
        return FALSE;
    }
    return TRUE;
}

受信はこんな感じ。
BOOL Recv(unsigned char Buffer[MAX_RECV_SIZE],unsigned long *Len)
{
    *Len=0;
    if(fp==NULL){
        SetLastError(ERROR_WPCAP);
        return FALSE;
    }
   
    int ret;
    const u_char *pkt_data;
    struct pcap_pkthdr *header;
    if(0<=(ret=pcap_next_ex(fp,&header, &pkt_data))){
        if(ret==1){ //パケットが読み込まれた
            *Len = header->caplen;
            memcpy(Buffer,pkt_data,*Len);
        }
        return TRUE;
    }
    SetLastError(ERROR_NEXT_EX);
    return FALSE;
}

ま、色々と面倒ですが。
これで、BufferにEthernetのフレームが丸ごと放り込まれる。あとはフレームを解析するだけなんだけど、、、ここでエイディアンの話があったりして。

Ethernetフレームの解析についてはもう教科書通りに淡々と行うしかない。
幸い、今回はARP(厳密に言えばARPリプライ)だけが必要なので、それ以外の処理はばっさり削除出来る。



で、実験的にARP送信まで実装してみたところ、うまく重複IPアドレスの検出が行えるようだ。
とにかく、動作については誰でも思いつく方法だと思うのだけど、とりあえずちゃんと動くようで、ほっと一安心。

ただ、
上記の通り、受信→解析をくるくる回す構造なので、重複IPの検出に関してはちと難しいものがあるという点、ARP送信に関して色々試行錯誤する必要がありそう・・・という感じで、まだまだ掛かりそうだ。

ちなみに、コードはほとんどCordzineのWinPcapを使用したパケットモニターの作成 から、かっぱらってきた。丁寧に解説されていたお陰で、非常に判りやすかった。多謝です。