[C 프로젝트] 패킷 분석기 Part 3. ethernet 프로토콜 분석기

 이번 강의는 “패킷 분석기 Part 3. ethernet 프로토콜 분석기 제작”입니다.

이전 강의  소스 다운로드

이번 강의 소스 다운로드

지난 강의에서 패킷 목록을 분석하는 ParseEther 함수의 반복문 내부에서 하는 일은 하나의 패킷 정보를 읽어와서 패킷 헤더 정보를 출력하는 것입니다. 이 부분에 읽어온 패킷 정보를 ethernet 프로토콜을 분석하는 함수를 호출하는 것을 추가로 작성합시다.

먼저 PcapFile.c 에서 패킷 목록을 분석하는 ParseEther 함수를 ParseEthers로 수정하세요. PcapFile.h와 Program.c에서 호출하는 부분도 수정합시다.

void ParseEthers(FILE *fp)//파일 명 수정
{
	PackHeader ph = { 0 };
	int pno=0;
	while (fread(&ph,sizeof(PackHeader),1,fp)==1)
	{
		pno++;
		ViewPacketHeaderInfo(&ph, pno);
		fread(buffer, sizeof(uchar), ph.caplen, fp);	
		ParseEther(buffer, ph.caplen); //추가한 내용
	}
}

 프로젝트에 EHEther.h 파일과 EHEther.c 파일을 추가하여 ethernet 프로토콜을 분석하는 부분을 구현할게요.

EHEther.h 작성

ethernet 프로토콜 스택(바로가기)은 목적지 MAC 주소와 발신지 MAC주소, L3 타입으로 구성합니다. 이를 구조체를 정의합시다. 그리고 IPv4와 ARP 프로토콜의 L3 type값을 상수로 정의하세요.

#pragma once
#include "PcapFile.h"

#define L3_IPv4 0x0800
#define L3_ARP 0x0806

typedef struct  _EtherHeader EtherHeader;
struct  _EtherHeader
{
	uchar dst_mac[6];
	uchar src_mac[6];
	ushort l3type;
};

void ParseEther(uchar *buffer, uint len);

EhEther.c 작성

EhEther.c 파일에 ParseEther 함수를 구현합시다.

입력 인자로 전달받은 buffer를 EtherHeader 포인터 형식으로 형식 변환합니다.

void ParseEther(uchar *buffer, uint len)
{
	EtherHeader *eh = (EtherHeader *)buffer;
}

그리고 L3 프로토콜의 위치를 계산하고 L3 패킷 크기를 계산합니다.

	uchar *next = buffer + sizeof(EtherHeader);
	len = len - sizeof(EtherHeader);

수집한 ethernet 프로토콜 정보를 보여줍니다. 이 부분은 ViewEther 함수로 작성하여 호출하세요.

	ViewEther(eh);

ethernet 헤더의 l3type 값에 따라 수행하는 선택문을 정의합니다. 만약 IPv4나 ARP일 때는 분석하는 함수를 호출하고 그렇지 않으면 지원하지 않는다고 알려줍니다. 이번 강의에서는 IPv4나 ARP일 때 나중에 정의하겠다는 메시지 출력으로 작성합시다. 다음은 ParseEther 함수입니다.

void ParseEther(uchar *buffer, uint len)
{
	EtherHeader *eh = (EtherHeader *)buffer;
	uchar *next = buffer + sizeof(EtherHeader);
	len = len - sizeof(EtherHeader);

	ViewEther(eh);
	switch (nthos(eh->l3type))
	{
		case L3_IPv4 : printf("IPv4: to be defined\n"); break;
		case L3_ARP : printf("ARP: to be defined\n"); break;
		default: printf("Not support\n");	break;
	}
}

이제 ethernet 헤더를 출력하는 함수를 작성합시다. 여기에서는 목적지 MAC 주소와 발신지 MAC 주소, L3Type을 출력합니다.

void ViewMac(const char *msg,uchar *base);
void ViewEther(EtherHeader *eh)
{
	printf("★★★★★     ethernet header    ★★★★★\n");
	ViewMac("dest", eh->dst_mac);
	ViewMac("source", eh->src_mac);
	printf("\tL3Type:%#x\n", ntohs(eh->l3type));
	printf("\t(IPv4:0x0800 ARP:0x0806)\n");
}
void ViewMac(const char *msg, uchar *base)
{
	printf("\t%s", msg);
	printf("%02x", base[0]);
	for (int i = 1; i < 6; i++)
	{
		printf(":%02x", base[i]);
	}
	printf("\n");
}

다음은 EHEther.c 파일의 소스 코드입니다.

#include "EHEther.h"

void ViewEther(EtherHeader *eh);
void ParseEther(uchar *buffer, uint len)
{
	EtherHeader *eh = (EtherHeader *)buffer;
	uchar *next = buffer + sizeof(EtherHeader);
	len = len - sizeof(EtherHeader);

	ViewEther(eh);
	switch (nthos(eh->l3type))
	{
		case L3_IPv4 : printf("IPv4: to be defined\n"); break;
		case L3_ARP : printf("ARP: to be defined\n"); break;
		default: printf("Not support\n");	break;
	}
}

void ViewMac(const char *msg,uchar *base);
void ViewEther(EtherHeader *eh)
{
	printf("★★★★★     ethernet header    ★★★★★\n");
	ViewMac("dest", eh->dst_mac);
	ViewMac("source", eh->src_mac);
	printf("\tL3Type:%#x\n", ntohs(eh->l3type));
	printf("\t(IPv4:0x0800 ARP:0x0806)\n");
}
void ViewMac(const char *msg, uchar *base)
{
	printf("\t%s", msg);
	printf("%02x", base[0]);
	for (int i = 1; i < 6; i++)
	{
		printf(":%02x", base[i]);
	}
	printf("\n");
}

PcapFile.h, PcapFile.c 파일과 Program.c 파일 내용

 수집한 패킷은 네트워크 바이트 정렬 방식으로 전달하는 것을 수집한 것입니다. 이를 호스트 정렬 방식으로 변경하기 위해 ntohs 함수를 호출한 부분이 있습니다. 이를 사용하기 위해서는 WinSock2.h 파일을 추가하고 ws2_32.dll 파일을 참조하겠다는 구문을 작성해야 합니다.

 이를 포함하여 수정한 PcapFile.h 파일의 소스 코드입니다.

#pragma once
#include <WinSock2.h>
#pragma comment(lib,"ws2_32")
#include <stdio.h>
typedef unsigned int uint;
typedef unsigned short ushort;
typedef unsigned char uchar;

typedef struct _PFHeader PFHeader; 
struct _PFHeader //패킷 파일 헤더
{
	uint magic;//0xA1B2C3D4
	ushort major;
	ushort minor;
	uint gmt_to_local;
	uint timestamp;
	uint max_caplen;
	uint linktype;
};

typedef struct _PACKETHEADER PackHeader;
struct _PACKETHEADER //패킷 헤더
{
	uint captime;//second
	uint caputime;//u second
	uint caplen;
	uint packlen;
};

#define PF_MAGIC 0xA1B2C3D4
#define LT_ETHER   0x01
void ParseEthers(FILE *fp);
int ParsePCapFile(FILE *fp, PFHeader *pfh);

다음은 PcapFile.c 파일의 소스 코드입니다.

#include "PcapFile.h"
#include "EHEther.h"
char buffer[0x100000];
void ViewPCapFile(PFHeader *pfh);
void ViewPacketHeaderInfo(PackHeader *ph,int pno);

void ParseEthers(FILE *fp)
{
	PackHeader ph = { 0 };
	int pno=0;
	while (fread(&ph,sizeof(PackHeader),1,fp)==1)
	{
		pno++;
		ViewPacketHeaderInfo(&ph, pno);
		fread(buffer, sizeof(uchar), ph.caplen, fp);	
		ParseEther(buffer, ph.caplen);
	}
}

int ParsePCapFile(FILE *fp, PFHeader *pfh)
{
	fread(pfh, sizeof(PFHeader), 1, fp);
	if (pfh->magic != PF_MAGIC)
	{
		return 0;
	}
	ViewPCapFile(pfh);
	return 1;
}
void ViewPCapFile(PFHeader *pfh)
{
	printf("=========== PCAP File 헤더 정보 ============\n");
	printf("\t 버전:%d.%d\n", pfh->major, pfh->minor);
	printf("\t최대 캡쳐 길이:%d bytes\n", pfh->max_caplen);
}
void ViewPacketHeaderInfo(PackHeader *ph, int pno)
{
	printf("!!! <%4d th> 프레임 !!!\n", pno);
	printf("패킷:%6d bytes 캡쳐:%6d\n", ph->packlen, ph->caplen);
}

Program.c 파일의 소스 코드입니다.

//https://ehpub.co.kr
//[언제나 프로젝트] C 패킷 분석기 Part 3. ethernet 프로토콜 분석기 구현
#include <stdli.h>
#include <stdio.h>
#include "PcapFile.h"
int main()
{
	char fname[256 + 1] = "";
	printf("분석할 pcap 파일명:");
	scanf_s("%s", fname, sizeof(fname));

	FILE *fp = 0;
	fopen_s(&fp, fname, "rb");
	if (fp == 0)
	{
		perror("파일 열기 실패");
		system("pause");
		return 0;
	}

	PFHeader pfh = { 0, };
	if (ParsePCapFile(fp, &pfh) == 0)
	{
		printf("PCAP 파일이 아닙니다\n");
		fclose(fp);
		system("pause");
		return 0;
	}

	switch (pfh.linktype)
	{
	case LT_ETHER: ParseEthers(fp); break;
	default: printf("Not Support\n"); break;
	}
	fclose(fp);
	system("pause");
	return 0;
}