• “크기” 와 “길이” 개념 구분하기 !!!

    • 크기 : 메모리 상에 할당된 용량
    • 길이 : 전송 데이터의 바이트 수
  • packet_handler 에서 다루는 패킷 데이터(pkt_data) 는 L2 Frame 내의 packet

  • Ethernet 헤더

    image.png

    • Ethernet 헤더 : Data 제외 14 byte 길이

      • MAC address (14 byte 길이)
        • Destination MAC address (48bit - 6byte 길이)
        • Source MAC address (48bit - 6byte 길이)
        • Type (2byte 길이) : 해당 이더넷 프레임의 페이로드 부분에 어떤 상위 계층(예: IP, ARP 등)의 데이터가 실려 있는지 식별하는 데 사용. 16비트 값
          • ex. 0x0800 → ipv4, 0x0806 → ARP
      • Data : 가변 데이터로 “payload” 를 의미한다. (보통 MAX 데이터 길이 : 1500 byte)
        • 이 payload 를 전송하기 위해서 Ethernet 헤더가 필요한 것
      • Trailer(Frame Check Sequence, CRC)
        • 해당 데이터가 있을 경우 길이가 늘어남

      ⇒ 보통 총 길이는 [14 byte + 1500 byte (packet) = 1514 byte]

  • Ethernet 헤더 분석 및 출력 - 헤더 정의

    #pragma pack(push, 1)
    typedef struct Etherheader {
    	unsigned char dstMac[6]; // 6byte 길이
    	unsigned char srcMac[6]; // 6byte
    	unsigned short type; // 2byte
    } EtherHeader;
    #pragma pack(pop)
    
    • #pragma pack(push, 1)

      • 구조체 각 멤버 변수들이 메모리에 어떻게 정렬되어 배치될지를 결정하는 규칙(member alignment) 을 변경한다.
      • 기존 규칙 default 값 : 시스템 (64 bit) 에 따른 값을 따름
        • 각 멤버 변수들 사이사이 패딩 바이트 가 생겨 에러 발생

      ⇒ 네트워크 에서는 기존 설정을 push 하고 member alignment 를 무조건 1 byte 단위로 바꿔야 한다!

    • #pragma pack(pop)

      ⇒ 네트워크에서 member alignment 를 1 byte 로 바꾼 후 Ethernet 헤더를 정의하고 나서 다시 pop 하여 기존 default member alignment 로 되돌린다.

  • (중요 !!!) Ethernet 헤더 구조체를 정의한 뒤, NIC로 수신한 프레임을 기반으로 Ethernet 헤더를 간단하게 분석하는 실습

    • (앞으로도 계속 필요한 프로젝트 설정 사전 작업) Visual Studio 2022 Community (개발 환경 : C++ [일반 아님])
      • 프로젝트 설정 (일반)
        • 외부 include 디렉터리 : C:\npcap\Include 추가
        • 라이브러리 디렉터리 : C:\npcap\Lib\x64 추가
      • 프로젝트 설정 (링커 - 입력)
        • 지연 로드된 DLL : wpcap.dll 추가
          • “지연 로드” : 프로그램 시작 시 즉시 DLL 을 로드하는 것이 아니라 호출 시점으로 늦춤
    #ifdef _MSC_VER
    /*
     * we do not want the warnings about the old deprecated and unsecure CRT functions
     * since these examples can be compiled under *nix as well
     */
    #define _CRT_SECURE_NO_WARNINGS
    #endif
    
    #include <pcap.h>
     // 추가 -> wpcap lib 를 사용했음을 선언
    #pragma comment(lib, "wpcap")
    //  추가 -> ws2_32 lib 를 사용함을 선언 추가 
    //  (htons - host to network short, ntohs - network to host short)
    #pragma comment(lib, "ws2_32")
    #include <stdio.h>
    #include <time.h>
    #ifdef _WIN32
    #include <tchar.h>
    
    /////////////////////////////////////////////////////////////
    // 추가 -> Ethernet 헤더 구조체 정의
    #pragma pack(push, 1)
    typedef struct EtherHeader {
    	unsigned char dstMac[6];
    	unsigned char srcMac[6];
    	unsigned short type;
    } EtherHeader;
    #pragma pack(pop)
    /////////////////////////////////////////////////////////////
    
    BOOL LoadNpcapDlls()
    {
    	_TCHAR npcap_dir[512];
    	UINT len;
    	len = GetSystemDirectory(npcap_dir, 480);
    	if (!len) {
    		fprintf(stderr, "Error in GetSystemDirectory: %x", GetLastError());
    		return FALSE;
    	}
    	_tcscat_s(npcap_dir, 512, _T("\\\\Npcap"));
    	if (SetDllDirectory(npcap_dir) == 0) {
    		fprintf(stderr, "Error in SetDllDirectory: %x", GetLastError());
    		return FALSE;
    	}
    	return TRUE;
    }
    #endif
    
    ////////////////////////////////////////////////////////////////////////
    /* prototype of the packet handler */
    void packet_handler(u_char* param, const struct pcap_pkthdr* header, const u_char* pkt_data);
    
    ////////////////////////////////////////////////////////////////////////
    
    int main()
    {
    	pcap_if_t* alldevs;
    	pcap_if_t* d;
    	int inum;
    	int i = 0;
    	pcap_t* adhandle;
    	char errbuf[PCAP_ERRBUF_SIZE];
    
    #ifdef _WIN32
    	/* Load Npcap and its functions. */
    	if (!LoadNpcapDlls())
    	{
    		fprintf(stderr, "Couldn't load Npcap\\n");
    		exit(1);
    	}
    #endif
    
    	/* Retrieve the device list */
    	// 1. 연결 가능한 NIC 네트워크 디바이스를 찾는다.
    	if (pcap_findalldevs(&alldevs, errbuf) == -1)
    	{
    		fprintf(stderr, "Error in pcap_findalldevs: %s\\n", errbuf);
    		exit(1);
    	}
    
    	/* Print the list */
    	for (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");
    	}
    
    	if (i == 0)
    	{
    		printf("\\nNo interfaces found! Make sure Npcap is installed.\\n");
    		return -1;
    	}
    
    	printf("Enter the interface number (1-%d):", i);
    	scanf("%d", &inum);
    
    	if (inum < 1 || inum > i)
    	{
    		printf("\\nInterface number out of range.\\n");
    		/* Free the device list */
    		pcap_freealldevs(alldevs);
    		return -1;
    	}
    
    	/* Jump to the selected adapter */
    	for (d = alldevs, i = 0; i < inum - 1; d = d->next, i++);
    
    	/* Open the device */
    	/* Open the adapter */
    	/* 2. 연결 가능한 NIC 디바이스들 중 특정 네트워크 디바이스에
    		  pcap 드라이버를 연결한다.
    	*/
    	if ((adhandle = pcap_open_live(d->name,	// name of the device
    		65536,			// portion of the packet to capture. 
    		// 65536 grants that the whole packet will be captured on all the MACs.
    		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 Npcap\\n", d->name);
    		/* Free the device list */
    		pcap_freealldevs(alldevs);
    		return -1;
    	}
    
    	printf("\\nlistening on %s...\\n", d->description);
    
    	/* At this point, we don't need any more the device list. Free it */
    	pcap_freealldevs(alldevs);
    
    	/* start the capture */
    	/* 3. pcap 드라이버와 연결되고 반환된 디바이스 어댑터 핸들러
    		  를 통해 패킷을 지속적으로 수집하고, packet_handler 함수
    		  를 콜백하여 처리한다.
    	*/
    	pcap_loop(adhandle, 0, packet_handler, NULL);
    
    	// 5. 디바이스 어댑터 핸들러를 종료한다.
    	pcap_close(adhandle);
    	return 0;
    }
    
    ////////////////////////////////////////////////////////////////////////////////
    
    // 추가 - 새롭게 4. 패킷 핸들러 함수를 작성 (분석 코드 작성)
    void packet_handler(u_char* param, const struct pcap_pkthdr* header, const u_char* pkt_data)
    {
    	/* 
    	   읽어온 Frame 내부 Packet 을 분석하기 위해
    	   Ethernet 헤더 구조체로 강제 형변환해서 저장
    	*/
    	EtherHeader* pEther = (EtherHeader*)pkt_data;
    
    	// time 구조체를 활용하여 timestamp 를 만들기 위함
    	struct tm ltime;
    	char timestr[16];
    	time_t local_tv_sec;
    
    	/* convert the timestamp to readable format */
    	// "패킷 헤더" 에서 패킷의 도착 시간(second) 을 가져와 출력
    	local_tv_sec = header->ts.tv_sec;
    	localtime_s(&ltime, &local_tv_sec);
    	// timestamp(timestr 이 작성한 포맷에 맞춰서 완성된다.)
    	strftime(timestr, sizeof timestr, "%H:%M:%S", &ltime);
    
    	// timestamp + "패킷 헤더" 에서 패킷의 도착 시간(microsecond) 을 가져와 출력
    	printf("arrived : %s.%.6d ", timestr, header->ts.tv_usec);
    
    	// "패킷 헤더" 에서 패킷의 길이를 가져와 출력
    	printf("len : %d, ", header->len);
    
    	// "패킷 데이터"(payload) 에서 출발지 MAC 주소를 가져와 출력
    	printf("Src MAC address : %02X-%02X-%02X-%02X-%02X-%02X -> ",
    		pEther->srcMac[0], pEther->srcMac[1], pEther->srcMac[2],
    		pEther->srcMac[3], pEther->srcMac[4], pEther->srcMac[5]
    	);
    
    	// "패킷 데이터"(payload) 에서 목적지 MAC 주소를 가져와 출력
    	printf("Dst MAC address : %02X-%02X-%02X-%02X-%02X-%02X, ",
    		pEther->dstMac[0], pEther->dstMac[1], pEther->dstMac[2],
    		pEther->dstMac[3], pEther->dstMac[4], pEther->dstMac[5]
    	);
    
    	// "패킷 데이터" type 을 가져와 출력 - ntohs : network to host short
    	/*
    		Ethernet Header 에서 type 필드는 네트워크 바이트 순서(big endian) 로 들어있음
    		이를 x86 시스템 에서 정상적으로 읽기 위해서 host order(little endian) 
    		으로 변환이 필요함!
    	*/
    	printf("type: %04X\\n", ntohs(pEther->type));
    }
    
    /////////////////////////////////////////////////////////////////////////
    

    image.png

    ⇒ 대부분 Ethernet 헤더는 Packet 을 포함하여 총 1514

  • CASE. Packet 포함 Ethernet 헤더 총 길이가 1514 가 아니고 그 이상으로 출력될 때

    • 네트워크 인터페이스 카드(NIC) 옵션이 설정되어 있어서 발생함

      • TCP Segmentation offload (large send offload)
        • CPU 가 해야할 일을 NIC 가 대신 수행하도록 하는 옵션
          • 스트림 데이터를 TCP 세그먼트로 분할하는 작업

      ⇒ Off 로 변경한다. : 10Gbps 급 고속 인터넷 환경에서 패킷 분석할 때 장애이슈가 있을 수 있음

      ⇒ Wi-fi 환경에서는 기본적을 LSO 를 하지 않아서 속성에 없을 수 있음

      image.png