mcommit's message

ソフトウェア開発の仕事をしているsimotinといいます。記事の内容でご質問やご意見がありましたらお気軽にコメントしてください\^o^/

C言語でEthernetFrameをpcap形式でダンプしてみる。

ネットワークの勉強のためだいぶ前に買っていた「ルーター自作でわかるパケットの流れ」を読んでいます。

ルーター自作でわかるパケットの流れ

ルーター自作でわかるパケットの流れ

書籍の中で、pcap形式について触れられていて、随分とシンプルなフォーマットだったので自分でもpcap形式のファイルをダンプしてみました。

準備

pcapに関する宣言などは、pcap.hでされているらしいのですが、pcap.hは標準ではインストールされていません。libpcap-dev のパッケージインストールを行います。


$ sudo apt install libpcap-dev

で pcap.hが/usr/includeにインストールされます。

pcap形式で出力するには struct pcap_file_header というファイルのヘッダ情報と
struct pcap_pkthdrというパケット単位のヘッダを付与し、後はフレームの中身を出力すればいいそうです。

ところで、struct pcap_file_header にいろいろ値を設定する必要があるがどこで定義されているかよくわかりません。
例えばEthernetフレームであれば、linktypeには LINKTYPE_ETHERNETとかを入れるようだがLINKTYPE_ETHERNETはpcap.hには見つからない。DLT_EN10MBでもよいらしいがこちらも見つからない。

いろいろ情報をあさっているとこちらのサイトがlibpcapの本家らしいということで、
www.tcpdump.org

とりあえずlibpcapのソースをダウンロードして調べてみます。
https://www.tcpdump.org/release/libpcap-1.9.0.tar.gz

grep してみると、
LINKTYPE_ETHERNETはpcap-common.cで宣言されている。ということはこの定義は非公開のようです。

一方、DLT_EN10MB は pcap/dlt.h で宣言されています。
こちらのファイルは apt で libpcap-dev をインストールしたときに /usr/include/pcap/dlt.h
としてインストールされていました。

ということで、ヘッダに必要な情報を適当に詰め込んで適当に動かしてみたところちゃんとwiresharkでも表示できました。

とりあえずキャプチャできてそうなコード

github.com

#include <stdio.h>
#include <net/if.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <net/ethernet.h>
#include <linux/if_packet.h>
#include <pcap.h>

#define TCPDUMP_MAGIC (0xA1B2C3D4)

static int openNetworkInterface(char *ifname);

int main(int argc, char **argv) {
	int sock;
	unsigned char recvBuf[1024*128];
	FILE *fpPcap;
	struct pcap_file_header pcapHeader;

	if (argc < 2) {
		fprintf(stderr, "must specify interface name.\n");
		exit(EXIT_FAILURE);
	}

	fprintf(stdout, "%s\n", argv[1]);
	sock = openNetworkInterface(argv[1]);
	if (sock < 0) {
		fprintf(stderr, "Open Interface [%s] failed.", argv[1]);
		exit(EXIT_FAILURE);
	}

	fpPcap = fopen("dump.pcap","wb");
	if (fpPcap == NULL)
	{
		fprintf(stderr, "Open Interface [%s] failed.", argv[1]);
		close(sock);
		exit(EXIT_FAILURE);
	}

	memset(&pcapHeader, 0, sizeof(struct pcap_file_header));
	pcapHeader.magic = TCPDUMP_MAGIC;
	pcapHeader.version_major = PCAP_VERSION_MAJOR;
	pcapHeader.version_minor = PCAP_VERSION_MINOR;
	pcapHeader.snaplen = 2048;
	pcapHeader.sigfigs = 0;
	pcapHeader.linktype = DLT_EN10MB;
	fwrite(&pcapHeader, sizeof(struct pcap_file_header), 1, fpPcap);
	while(1) {
		int recvSize;
		struct pcap_pkthdr pktHeader;

		recvSize = read(sock, recvBuf, sizeof(recvBuf));
		if (recvSize < 0)
		{
			fprintf(stderr, "read error [%d]\n", recvSize);
			continue;
		}

		fprintf(stdout, "recvSize:[%d]\n", recvSize);
		memset(&pktHeader, 0, sizeof(struct pcap_pkthdr));

		gettimeofday(&(pktHeader.ts), NULL);
		pktHeader.caplen = recvSize;
		pktHeader.len = recvSize;

		fwrite(&pktHeader, sizeof(struct pcap_pkthdr), 1, fpPcap);
		fwrite(recvBuf, recvSize, 1, fpPcap);
	}

	close(sock);
	fclose(fpPcap);
	exit(EXIT_SUCCESS);
}

int openNetworkInterface(char *ifname) {
	int ret;
	int sock;
	struct ifreq ifreq;
	struct sockaddr_ll sa;

	sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
	if (sock < 0) {
		fprintf(stderr, "socket failed %d\n", sock);
		return -1;
	}

	memset(&ifreq, 0, sizeof(struct ifreq));
	strncpy(ifreq.ifr_name, ifname, sizeof(ifreq.ifr_name)-1);

	ret = ioctl(sock, SIOCGIFINDEX, &ifreq);
	if (ret < 0) {
		fprintf(stderr, "ioctl failed [%d]\n", ret);
		close(sock);
		return -1;
	}

	sa.sll_family=PF_PACKET;
	sa.sll_protocol=htons(ETH_P_ALL);
	sa.sll_ifindex=ifreq.ifr_ifindex;

	ret = bind(sock, (struct sockaddr *)&sa, sizeof(struct sockaddr_ll));
	if (ret < 0) {
		fprintf(stderr, "bind failed [%d]\n", ret);
		close(sock);
		return -1;
	}

	return sock;
}

できていないこと

  1. フレームのタイプのチェックとかしてないけどいいんだろうか(ARPとかICMPとか・・・)
  2. 異常系あまりケアしてません。Ctrl-Cとかで止めると多分最後のパケットは適切にダンプされない。

関連書籍

著者の小俣さんの続編的な書籍!?が出版されていて、この本も買ってみたのですが、積本になってしまっています。
せっかく買ったので読みたいけど読めていない・・・

感想

自分でパケットキャプチャしてみて、ゴニョゴニョするのってなんかかっこいい。