とにかく面倒。
いや、Cそのものもかなり面倒に思うのだけど。。。
とにかく、ARP送信とARP受信が出来ればいいという事もあるし、スレッドは別の方法を採るので、あくまでシングルスレッドの中でARPを送信しつつ受信する事を考えてみた、、、こんな感じで。
※そもそもセグメント全体をARPでスキャンする事を考えているので、マルチスレッド化は必須。
//---------------------------------------------------------------------------RECV_TIMERで設定した時間が来るまではひたすら受信しながら回り続ける。
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;
}
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を使用したパケットモニターの作成 から、かっぱらってきた。丁寧に解説されていたお陰で、非常に判りやすかった。多謝です。
コメント