2011년9월28일_pcap_이더넷구조체를 가리키는 구조체포인터를 사용하여 캡쳐한 패킷의 헤더를 추출하자.




● 프로세서간 통신 ┬ 내부 – IPC
                        └ 외부 – Network
이건 전에 했으니 생략


● 슬라이딩 윈도우  

슬라이딩 윈도
(Sliding window)는 두 개의 네트워크 호스트간의 패킷의 흐름을 제어하기 위한 방법이다.

TCP와 같이 데이터의 전달을 보증하는 프로토콜에서는 패킷 하나하나가 전달되었음을 확인 신호(acknowledgement, 이하 ACK)를 받아야하며, 만약 패킷이 중도에 잘못되었거나 분실되어 확인받지 못하는 경우, 해당 패킷을 재전송해야하는 필요가 있다. 슬라이딩 윈도는 일단 '윈도(메모리 버퍼의 일정 영역)'에 포함되는 모든 패킷을 전송하고, 그 패킷들의 전달이 확인되는대로 이 윈도를 옆으로 옮김(slide)으로서 그 다음 패킷들을 전송하는 방식이다.

슬라이딩 윈도는 아직 확인을 받지 않고도 여러 패킷을 보내는 것을 가능케 하기 때문에, 매번 전송한 패킷에 대해 확인을 받아야만 그 다음 패킷을 전송하는 방법(stop-and-wait)을 사용하는 것보다 훨씬 네트워크를 효율적으로 사용할 수 있다.
[출처] 위키백과 http://ko.wikipedia.org/wiki/%EC%8A%AC%EB%9D%BC%EC%9D%B4%EB%94%A9_%EC%9C%88%EB%8F%84

교양이니까~

image

버퍼에 들은 데이터를 포장해 조각조각 전송하고 상대방이 잘 받았으면 ACK응답을 하고 아니면 일정시간 기다린 후 재전송한다.
다음 데이터도 포장해 전송하고 마찬가지로 ACK응답처리를 한다.

image

상기의 돗식은 출발지에서 목적지까지의 경로를 그린 ㄳ이고,
먼저 전송한 패킷#1이 항상 먼저 도착한다는 보장은 없다.
경로는 어떻게 선택되는지 설명을 놓쳐 모르겠으나 허브와 PC간 MAC과 IP교환에 대한 말씀을 들어보니..
라우터가 임의로 선택하는 것 같고 왠지 자세히 들어가면 복잡할 듯 하다.
하나를 배우면 열 개를 아는게 아니라 하나도 모르는게 문제다. (흑흑)

image 

상기의 도식은 패킷의 크기(데이터)를 ACK응답을 받은 경우 상황에 따라 크기를 점차 늘려가 속도를 증가시키는 법을 그린 것이다.




● 이더넷 헤더 파일

image image

# cd /usr/include
# cd net
# vi ethernet.h

이더넷 헤더 파일 ethernet.h는 기본 헤더파일 경로의 하위 net 디렉토리에 있다.
vi에디터로 열어보자.

image

초반에 이더넷헤더의 원형이 정의되어 있고 10Mb/s라는 것을 보니 100Mb/s는 다른 헤더를 사용하는 것 같다.

   1: /* 10Mb/s ethernet header */
   2: struct ether_header
   3: {
   4:   u_int8_t  ether_dhost[ETH_ALEN];  /* 도착지 LAN카드 MAC주소 (6Bytes) */
   5:   u_int8_t  ether_shost[ETH_ALEN];  /* 출발지 LAN카드 MAC주소 (6Bytes) */
   6:   u_int16_t ether_type;            /* 패킷 타입 ID (2Bytes) */
   7: } __attribute__ ((__packed__));    // #pragma pack 1 과 비슷한 것으로 
   8:                   // 컴파일시 구조체의 크기가 14Bytes가 되도록
   9:                   // 최적화하고 없으면 32bit시스템에 최적화해
  10:                   // 16Bytes가 된다.  

32bit CPU에선 모든 레지스터가 32bit이고 메모리도 32bit단위로 취급하니 맨 아래의 16bit ID는 32bit로 크게 확장된다.
ETH_ALEN은 어디에 정의 되어 있을까? linux/if_ether.h에 있다. (너무 쉽게 낼름낼름 먹는 것 같아)

image

EHT_ALEN은 6으로 정의되어 있다.
u_int8_t는 unsigned char로 1Byte이니 배열로 6개의 원소를 선언할 경우 6Bytes가 된다. (아직 선언한 것은 아니다.)

image

ether_type은 상기와 같은 값을 가질 수 있고 빅엔디안방식으로 버퍼에 저장된다.
(Intel CPU는 리틀엔디안이고, 네트워크는 빅엔디안방식을 주로 쓰는 것 같아)

   1: /* Ethernet protocol ID's */
   2: #define    ETHERTYPE_PUP        0x0200      /* Xerox PUP 이건 설명 안 함 */
   3: #define    ETHERTYPE_IP        0x0800        /* 헤더 다음에 오는 데이터가 IP주소를 가지고 있다.  */
   4: #define    ETHERTYPE_ARP        0x0806        /* Address resolution - MAC을 요청 */
   5: #define    ETHERTYPE_REVARP    0x8035        /* Reverse ARP - IP를 요청 */

MAC요청은 IP주소로 MAC주소를 알아내는 것이고,
IP요청은 MAC주소로 IP주소를 알아내는 것이다.
(왠지 이런 개념은 windows API에 컨트롤ID와 핸들 얻어 내는 것하고 소켓 프로그래밍에서도 비슷한 것을 본 기억이 난다.)

image

어지럽게 떠돌아 다니는 패킷을 하나 건져 보니,
목적지의 MAC주소가 학교에 있는 허브가 아니고,
출발지의 MAC주소는 나의 LAN카드 MAC주소가 아니다.
마지막 type ID는 08 00으로 빅엔디안방식으로 그대로 읽으면 0x0800으로 다음에 오는 데이터가 IP주소이다라는 것까지 알 수 있다.
(이더넷헤더의 정의를 알았으니 실제 수집한 패킷의 헤더에 표기하기 위해 스크린샷을 찍은 것임.)




● LAN도식과 ehter_type의 ID값 설명

image

공유기(hub + 라우터)에 연결된 모든 컴퓨터는 부팅 후 공유기로 자신의 MAC주소와 IP주소를 알려준다.
공유기는 이 주소들을 수집하여 자신의 메모리에 저장해 두고,
컴퓨터(host)들이 전송해 온 패킷을 분석하는데 참조한다.

image

패킷낚시를 계속 하다 보면 상기와 같은 패킷이 걸리는데 처음 오는 목적지의 MAC주소가 모두 1이고, (0xFF 0xFF 0xFF 0xFF 0xFF 0xFF)
출발지의 MAC주소는 내 PC의 MAC주소는 아니고,
type ID는 0x08 0x06으로 빅엔디안방식으로 읽으면 0x0806으로 ARP로 MAC주소를 요청하는 것이다.
이더넷헤더는 MAC주소가 필요하니 상대방의 IP주소로 MAC주소를 얻어내는 일이 빈번하게 일어나는 것 같다.



● Packet Analayzer 프로젝트

image

소스인사이드로 소스코드를 보기 좋게 편집하자.

▷ main.c

   1: #include <stdio.h>
   2: #include <pcap/pcap.h>
   3: #include "HexaView.h"
   4: #include "L1_Ethernet.h"
   5:  
   6: int main()
   7: {
   8:     char errbuf[PCAP_ERRBUF_SIZE];        //에러가 나면 여기에 기록됨.
   9:     char *cpNIC_Name;    //NIC는 장치 (Network Interface Card)
  10:     pcap_t *stpNIC;        // 구조체포인터
  11:     struct pcap_pkthdr stInfo;        // 정보를 담을 구조체
  12:     const u_char *ucpData;    // 데이터
  13:     
  14:     
  15:     // 1. 네트웍 장치명 읽어오기
  16:     cpNIC_Name = pcap_lookupdev(errbuf);    // 장치이름을 알아오고,
  17:     if(NULL == cpNIC_Name)    // 에러난 경우 에러버퍼에 에러가 난 이유를 저장.
  18:     {
  19:         printf(errbuf);        
  20:         putchar('\n');
  21:         exit(-1);        //exit()가 나은가?
  22:         //return -1;        //리턴이 나은가?
  23:     }
  24:     
  25:     printf(cpNIC_Name);
  26:     putchar('\n');
  27:     
  28:  
  29:     // 2. 네트웍 장치 열기
  30:     //                         장치명, 나중에 설명, 에러버퍼   
  31:     stpNIC = pcap_open_live(cpNIC_Name, 1500, 1, 0, errbuf);
  32:     if(NULL == stpNIC)    //에러가 난 경우
  33:     {
  34:         printf(errbuf);
  35:         putchar('\n');
  36:         exit(-2);
  37:         //return -2;
  38:     }
  39:  
  40:     printf("패킷캡쳐 오픈 successful(성공)\n");
  41:     
  42:     // 3. 패킷분석을 위해 패킷캡쳐 
  43:     ucpData = pcap_next(stpNIC, &stInfo);
  44:  
  45:     // 4. 패킷내용을 출력
  46:     HexaView(ucpData, 160);
  47:     //MSDFunction(ucpData, 10);
  48:  
  49:     // 5. 이더넷 헤더 
  50:     L1_Ethernet(ucpData);
  51:  
  52:     // last. 열린 네트웍장치를 닫는다.
  53:     pcap_close(stpNIC);        
  54:     
  55:     return 0;
  56: }
  57:  

▷ L1_Ethernet.c

   1: #include "L1_Ethernet.h"
   2:  
   3: void L1_Ethernet(const void *vpData)
   4: {
   5:     const struct ether_header *stpEth = vpData;
   6:  
   7:     printf("%02X:%02X:%02X:%02X:%02X:%02X->"
   8:         "%02X:%02X:%02X:%02X:%02X:%02X\n"
   9:         , stpEth->ether_shost[0]
  10:         , stpEth->ether_shost[1]
  11:         , stpEth->ether_shost[2]
  12:         , stpEth->ether_shost[3]
  13:         , stpEth->ether_shost[4]
  14:         , stpEth->ether_shost[5]    
  15:         , stpEth->ether_dhost[0]
  16:         , stpEth->ether_dhost[1]
  17:         , stpEth->ether_dhost[2]
  18:         , stpEth->ether_dhost[3]
  19:         , stpEth->ether_dhost[4]
  20:         , stpEth->ether_dhost[5]);
  21:  
  22:     return ;
  23: }
  24:  

 

  49:     // 5. 이더넷 헤더 
  50:     L1_Ethernet(ucpData);

main.c의 50행에서 L1_Ethernet을 호출하며 캡쳐한 패킷을 저장하고 있는 메모리의 주소를 넘겨준다.

   3: void L1_Ethernet(const void *vpData)
   5:     const struct ether_header *stpEth = vpData;

L1_Ethernet()의 첫 번째 인자는 const void *타입으로 어떤 자료형이라도 받아 들일 수 있다.
이더넷구조체 ether_header의 구조체포인터 변수 stpEth를 선언하고 받은 주소값으로 초기화한다.

   7:     printf("%02X:%02X:%02X:%02X:%02X:%02X->"
   8:         "%02X:%02X:%02X:%02X:%02X:%02X\n"
   9:         , stpEth->ether_shost[0]
  10:         , stpEth->ether_shost[1]
  14:         , stpEth->ether_shost[5]    
  15:         , stpEth->ether_dhost[0]
  16:         , stpEth->ether_dhost[1]
  20:         , stpEth->ether_dhost[5]);

printf()로 이더넷 구조체 포인터 변수 stpEth가 보는 관점으로 메모리의 내용을 출력한다.


image

ucpData는 unsigned char *형이니 1Byte씩 읽을 수 있고,
stpEth는 구조체포인터 변수니 이더넷구조체대로 읽을 수 있다.