winpcapを使ってTCPパケットを送れるか試す。

まだ完全ではないが、進展があったのでメモ。
前記事のことがあったので、早速winpcapを使ってコードを書く。
SYNパケットを送りつける手抜きコード。


windows xp sp3 + MinGW(GCC4.4.0)で確認しています。
winpcapが必要です。フリー&&BSDライセンスで使えます。
winpcap
http://www.winpcap.org/


本体のソースコードはexampleのsendpackをほぼそのまま使ってます。

ファイル構成

  • pcap_send.c (本体ソース)
  • pcap_send.mak (makefile)
  • include (pcap ヘッダー郡)
  • lib (pcap ライブラリ郡)

pcap_send.c

#include <stdlib.h>
#include <stdio.h>
#include <pcap.h>

int main(int argc, char**argv){
	pcap_if_t *alldevs, *d;
	pcap_t *fp;
	char errbuf[PCAP_ERRBUF_SIZE];
	int i;
	// サンプルデータ
	const char packet[] = 
		"\x00\x30\x13\x11\x3E\xA1" "\x00\x1F\xC6\x66\x97\x4D" "\x08\x00"  // ETHERNET(14byte)
		"\x45\x00\x00\x28\x00\x00\x00\x00\x40\x06\x7A\xEB\xC0\xA8\x0B\x02\xC0\xA8\x0B\xFE" // IP (20byte)
		"\x10\x01\x10\x00\x10\x00\x00\x00\x00\x00\x00\x00\x50\x02\x02\x00\xE5\x90\x00\x00" // TCP(20byte)
	;
	/* Retrieve the device list */
	if(pcap_findalldevs(&alldevs, errbuf) == -1)
	{
		fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);
		return -1;
	}
	
	/* Check the validity of the command line */
	if (argc < 2)
	{
		/* Print the list */
		for(i=0, d=alldevs; d; d=d->next)
		{
			printf("%d. %s", i++, d->name);
			if (d->description)
				printf(" (%s)\n", d->description);
			else
				printf(" (No description available)\n");
		}
		
		printf("usage: %s [Num]", argv[0]);
		return 1;
	}
	
	// select adapter
	i = atoi(argv[1]);
	for(d=alldevs; d&&i; d=d->next, i--);	
	if(i != 0 || d ==NULL){
		printf("error \n");
		return 1;
	}
	
	printf("use adapter: %s(%s)\n", d->description, d->name);
	
    /* Open the adapter */
	if ((fp = pcap_open_live(d->name,		// name of the device
							 65536,			// portion of the packet to capture. It doesn't matter in this case 
							 1,				// promiscuous mode (nonzero means promiscuous)
							 1000,			// read timeout
							 errbuf			// error buffer
							 )) == NULL)
	{
		fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n", argv[1]);
		return 2;
	}
	
		/* Send down the packet */
	if (pcap_sendpacket(fp,	// Adapter
		packet,				// buffer with the packet
		sizeof(packet)					// size
		) != 0)
	{
		fprintf(stderr,"\nError sending the packet: %s\n", pcap_geterr(fp));
		return 3;
	}

	pcap_close(fp);	
	return 0;
}


makefile

# Makefile for cygwin gcc
# Nate Lawson <nate@rootlabs.com>

PCAP_PATH = ./lib
CC = gcc
CFLAGS = -g -O2 -I ./include
OBJS = pcap_send.o
LIBS = -L ${PCAP_PATH} -lwpcap

all: ${OBJS}
	${CC} ${CFLAGS} -o pcap_send.exe ${OBJS} ${LIBS}

clean:
	rm -f ${OBJS} pcap_send.exe

.c.o:
	${CC} ${CFLAGS} -c -o $*.o $<

実行

pcap_send.exe 1

use adapter: Marvell Gigabit Ethernet Controller (Microsoft's Packet cheduler) (\Device\NPF_{--でばいすID?--})

数字を引数に指定しないで実行すると、使えるアダプター一覧が出るだけです。
上から0番目としています。
仮装環境とか入れているといくつも出るかもしれません。
数字を引数に指定すると、そのアダプターを使ってsendします。
1としたのは、僕の環境でインターネットに繋がっているアダプターが1番目にあったからです。


上手くいくと、同じパケットをキャプチャソフトが拾ってくれます。こんな感じに。

注意

実際に届いているかどうかのチェックなどは全くしてません。
次はecho鯖を用意して、ちゃんと3ウェイハンドシェイクをやるか(ack+synを返すか)調べます。
もしダメなようなら、この記事は消えるでしょう。


ソースコードを見てわかるように、あらかじめ作ったパケットにMACアドレスをつけて送りつけただけです。
ちゃんと送りなおしたい場合は、
イーサネットフレーム部のMACアドレス、IPヘッダ部のIPアドレスチェックサムTCPヘッダ部のポート番号とチェックサムを適宜対応するように書き換えます。