4. 패킷 분석기 예광탄 - IPv4 프로토콜 스택 분석 
안녕하세요. 언제나 휴일, 언휴예요.
지난 게시글에서는 pcap 포멧 파일을 로딩하여 ethernet 프로토콜 스택을 분석하는 예광탄을 만들어 보았어요.
더보기 
이번에는 IPv4 프로토콜 스택 분석하는 부분을 작성할 거예요. 
더보기 
빨간색으로 테두리 안에 있는 내용이 이번에 추가한 기능에 의해 출력하는 부분이예요.
현재 작성하는 것이 예광탄이긴 하지만 이미 작성한 부분만 보더라도 헤더 파일없이 소스 코드에만 작성하는 것은 장기적으로 보았을 때 효율이 떨어질 것 같아요. 이번에는 이제까지 작성한 것을 다시 한 번 살펴보면서 헤더 파일과 소스 파일로 구분하기로 할게요. 물론 아직까지는 프로그램 설계는 생략하며 소스 파일을 분할하지는 않을 거예요.
먼저 예광탄에서 사용자 정의 형식 부분은 ehpacket.h 파일에 작성할게요.
헤더 파일 코드 보기 접기  
  
#pragma   once 
#pragma   warning ( disable :4996) 
#include   <WinSock2.h> 
#pragma   comment ( lib , "ws2_32" ) 
#pragma   pack (1) 
typedef   unsigned   short   ushort ; 
typedef   unsigned   char   uchar ; 
typedef   unsigned   int   uint ; 
  
//                          pcap fileheader 
//  _______________________________________________________________________ 
//  |        4            |    2      |     2     |            4           | 
//  _______________________________________________________________________ 
//  |  magic(0xa1b2c3d4)  | maj.ver   |  min.ver  | gmt to localcorrection | 
//  _______________________________________________________________________ 
//  |       4             |           4           |            4           | 
//   _______________________________________________________________________ 
//  |    캡쳐한 시각       |   snap 의 최대 길이    |     datalink type      |  
//  ________________________________________________________________________ 
  
typedef   struct   _pcap_file_header 
{ 
#define   MAGIC    0xa1b2c3d4  //pcap magic 
     int  magic; 
     unsigned   short  version_major; 
     unsigned   short  version_minor; 
     int  thiszone;  /* gmt to localcorrection */ 
     unsigned  sigfigs;  /* accuracy oftimestamps */ 
     unsigned  snaplen;      /* maxlength savedportion of each pkt */ 
     unsigned  linktype;     /* datalink type (LINKTYPE_*) */ 
} pcap_file_header ; 
  
typedef   struct   _timeval 
{ 
     long     tv_sec;          /* seconds*/ 
     long     tv_usec;         /*andmicroseconds */ 
} timeval ; 
  
//                                   pcapheader 
//  _______________________________________________________________________________ 
//  |                   8                 |      4            |        4          | 
//   ______________________________________________________________________________ 
//  | seconds(4)       | micro seconds(4) |     캡쳐한 길이    |        패킷 길이   |  
//  _______________________________________________________________________________ 
  
typedef   struct   _pcap_header 
{ 
     timeval  ts;     /*time stamp */ 
     unsigned  caplen;  /* length of portionpresent */ 
     unsigned  len;      /*length thispacket (off wire) */ 
} pcap_header ; 
  
  
//                 ethernet protocol stack 
//   ____________________________________________________________________________ 
//  |                6               |             6                |       2   | 
//  ____________________________________________________________________________ 
//  |         dest mac address       |       src mac address        |     type  | 
//  ____________________________________________________________________________ 
  
#define   MAC_ADDR_LEN     6 
typedef   struct   _ethernet 
{ 
     unsigned   char  dest_mac[ MAC_ADDR_LEN ]; 
     unsigned   char  src_mac[ MAC_ADDR_LEN ]; 
     unsigned   short  type; 
} ethernet ; 
  
//                          IPv4 protocol stack 
//   _____________________________________________________________________________________ 
//  |    4      |    4      |        8           |              16                       | 
//   _____________________________________________________________________________________ 
//  |   version |  HLEN     |    service type    |         total length                  | 
//   _____________________________________________________________________________________ 
//  |                      16                    |     3   |              13             | 
//   _____________________________________________________________________________________ 
//  |                 identification             |  Flags  |      Fragment offset        | 
//   _____________________________________________________________________________________ 
//  |         8             |        8           |              16                       | 
//   _____________________________________________________________________________________ 
//  |    Time To Live       |     Protocol       |            Header Checksum            | 
//   ____________________________________________________________________________________ 
//  |                                           32                                       | 
//   ____________________________________________________________________________________ 
//  |                                   Source IPv4 Address                              | 
//   ____________________________________________________________________________________ 
//  |                                           32                                       | 
//   ____________________________________________________________________________________ 
//  |                                 Destination IPv4 Address                           | 
//   ____________________________________________________________________________________ 
//  |                                     0~320(40  바이트)                               |  
//   ____________________________________________________________________________________ 
//  |                                    Options and Padding                             | 
//   ____________________________________________________________________________________ 
  
  
typedef   struct   _iphdr 
{ 
     uchar   hlen : 4;  //header length , 1 per 4bytes 
     uchar   version : 4;  //version , 4 
     uchar   service; 
     ushort  tlen;  //total length 
     ushort     id;  //identification    
     ushort     frag; 
#define   DONT_FRAG (frag)   (frag & 0x40) 
#define   MORE_FRAG (frag)   (frag & 0x20) 
#define   FRAG_OFFSET (frag) (ntohs(frag) & (~0x6000)) 
     uchar  ttl; //time to live 
     uchar  protocol; 
     ushort  checksum; 
     uint  src_address; 
     uint  dst_address; 
} iphdr ; 
접기 
이번에 추가한 부분은 IPv4 헤더 부분이예요. 
typedef   struct   _iphdr 
{ 
 버전 정보와 헤더 길이가 4비트씩으로 구성하고 있으며 host byte order가 little endian이라는 가정에서 구조체에 두 개의 멤버의 순서를 바꾸었어요.    
     uchar   hlen : 4;  //header length , 1 per 4bytes 
     uchar   version : 4;  //version , 4 
그리고 TOS(Type of Service)라 불렀던 Service 멤버와 전체 길이와 패킷을 구분하기 위한 ID 멤버가 있습니다. 
     uchar   service; 
     ushort  tlen;  //total length 
     ushort     id;  //identification    
이어서 단편화 패킷인지 구분하고 단편화 패킷일 때 단편화 offsets인 멤버가 있어요.   
     ushort     frag; 
#define   DONT_FRAG (frag)   (frag & 0x40) 
#define   MORE_FRAG (frag)   (frag & 0x20) 
#define   FRAG_OFFSET (frag) (ntohs(frag) & (~0x6000)) 
그리고 경유할 수 있는 최대 라우터 수를 의미하는 TTL과 프로토콜(ICMP, IGMP, TCP, UDP, OSPF)와 체크섬 이 있죠.  
     uchar  ttl; //time to live 
     uchar  protocol; 
     ushort  checksum; 
마지막으로 발신지 주소와 목적지 주소가 있습니다. 
     uint  src_address; 
     uint  dst_address; 
} iphdr ; 
이제 소스 코드를 살펴보아요. 
소스 파일 코드 보기 접기  
  
// 패킷 분석기 예광탄 - IPv4  프로토콜 스택 분석 
// 언제나 휴일, http://ehclub.co.kr  
// 코드 변경없이 사용 및 배포할 수 있습니다.  
  
#include   "ehpacket.h" 
#include   <stdio.h> 
  
#define   MAX_PACKET   10 
pcap_header  headers[ MAX_PACKET ]; // 패킷 헤더들을 보관할배열 
int  pcnt;  // 패킷 개수 
int  Parsing( FILE  *fp); 
int  main() 
{ 
     char  fname[256]; 
     FILE  *fp = 0; 
    printf( " 파일 명:"  ); 
    gets_s(fname,  sizeof (fname)); 
    fopen_s(&fp, fname,  "rb" ); // 전달받은 파일을 읽기/ 바이너리모드로 열기 
    Parsing(fp); // 분석하기 
    fclose(fp); // 파일 닫기 
     return  0; 
} 
  
void  ParsingEthernet( FILE  *fp); 
int  Parsing( FILE  * fp ) 
{ 
     pcap_file_header  pfh; 
    fread(&pfh,  sizeof (pfh), 1,  fp ); //pcap  파일 헤더 읽기     
     if  (pfh.magic !=  MAGIC )  // 매직이 다르면 
    { 
        printf( "this file format is not correct \n" ); 
         return  -1; 
    } 
    printf( "version:%d.%d\n" , pfh.version_major, pfh.version_minor); //pcap  헤더 정보 출력 
    
     switch  (pfh.linktype) // 링크 타입에 따라 
    { 
     case  1:ParsingEthernet( fp );  break ;  //Ethernet  방식으로 분석 
     case  6:printf( "Not support Token Ring\n" ); break ; 
     case  10:printf( "Not support FDDI\n" );  break ; 
     case  0:printf( "Not support Loopback\n" );  break ; 
     default :printf( "Unknown\n" );  break ; 
    } 
     return  0; 
} 
  
void  ViewPacketHeader( pcap_header  *ph); 
void  ViewEthernet( char  *buf); 
void  ParsingEthernet( FILE  * fp ) 
{ 
     char  buf[65536]; 
     pcap_header  *ph = headers; //ph 를 패킷 헤더의 시작 위치로 초기화 
     int  i = 0; 
     while  (feof( fp ) == 0)  // 파일의 끝이 아니면 반복 
    { 
         if  (fread(ph,  sizeof ( pcap_header ), 1,  fp ) != 1) // 패킷 헤더 읽기를 실패하면 
        { 
             break ; // 루프 탈출 
        } 
         if  (pcnt ==  MAX_PACKET )  // 예광탄에서 분석할 수 있게 설정한 패킷 수에 도달 
        { 
             break ; // 루프 탈출 
        } 
        ViewPacketHeader(ph);  // 패킷 헤더 정보 출력 
        fread(buf, 1, ph->caplen,  fp );  // 패킷 읽기 
        ViewEthernet(buf);  // 이더넷 정보 출력 
        ph++; //ph 를 다음 위치로 이동 
    } 
} 
  
  
void  ViewPacketHeader( pcap_header  * ph ) 
{ 
    pcnt++; // 패킷 개수를 1  증가 
    printf( "\nNo:%dtime:%08d:%06d caplen:%u length:%u \n" , 
        pcnt,  ph ->ts.tv_sec,  ph ->ts.tv_usec,  ph ->caplen,  ph ->len); 
} 
  
void  ViewMac( unsigned   char  *mac); 
void  ViewIP( char  *buf); 
void  ViewARP( char  *buf); 
void  ViewEthernet( char  * buf ) 
{ 
     ethernet  *ph = ( ethernet  *) buf ;  // 패킷 버퍼를 ethernet  구조체 포인터로 형 변환 
    printf( "===========ETHERNET Header==============\n" ); 
    printf( "dest mac:0x" ); 
    ViewMac(ph->dest_mac); //MAC  주소 출력 
    printf( "  src mac:0x" ); 
    ViewMac(ph->src_mac); //MAC  주소 출력 
    printf( "  type:%#x\n" , ntohs(ph->type));    
     //Link  타입을 출력(2 바이트 이상데이터는 network byte order -> host byte order  로 변환해야 함 
    
     switch  (ntohs(ph->type)) 
        { 
     case  0x800:ViewIP( buf  +  sizeof ( ethernet ));  /*IP  정보 출력 */    break ; 
     case  0x806:ViewARP( buf  +  sizeof ( ethernet ));  /*ARP  정보 출력*/    break ; 
     default :printf( "Not support Protocol\n" );  break ; 
    } 
} 
  
void  ViewMac( unsigned   char  * mac ) 
{ 
     int  i; 
     for  (i = 0;i <  MAC_ADDR_LEN ; ++i) 
    { 
        printf( "%02x " ,  mac [i]); 
    } 
} 
  
ushort  ip_checksum( ushort  *base,  int  len); 
void  ViewIP( char  * buf ) 
{ 
     IN_ADDR  addr; 
     iphdr  *ip = ( iphdr  *) buf ;  // 패킷 버퍼를 ethernet  구조체 포인터로 형변환 
    printf( "\n=========== IPv4 Header ==============\n" ); 
    addr. s_addr  = ip->src_address; 
    
    printf( "src:%s, " , inet_ntoa(addr)); 
  
    addr. s_addr  = ip->dst_address; 
    printf( "dst:%s\n" , inet_ntoa(addr)); 
  
    printf( "header length:%d bytes, " , ip->hlen * 4); 
    printf( "version:%d, " , ip->version); 
    printf( "total length:%d bytes\n" , ntohs(ip->tlen)); 
    printf( "id:%d, " , ntohs(ip->id)); 
     if  ( DONT_FRAG (ip->frag)) 
    { 
        printf( "Don't Fragment\n" ); 
    } 
     else 
    { 
         if  ( MORE_FRAG (ip->frag) == 0) 
        { 
            printf( "last fragment, " ); 
        } 
        printf( "offset:%d " ,  FRAG_OFFSET (ip->frag)); 
    } 
     if  (ip_checksum(( ushort  *) buf ,  sizeof ( iphdr )) == 0) 
    { 
        printf( "checksum is correct, " ); 
    } 
     else 
    { 
        printf( "checksum is not correct, " ); 
    } 
    printf( "TTL:%d\n" , ip->ttl); 
     switch  (ip->protocol) 
    { 
     case  1: printf( "ICMP\n" );  break ; 
     case  2: printf( "IGMP\n" );  break ; 
     case  6: printf( "TCP\n" );  break ; 
     case  17: printf( "UDP\n" );  break ; 
     case  89: printf( "OSPF\n" );  break ; 
     default : printf( "Not support\n" );  break ; 
    } 
} 
void  ViewARP( char  * buf ) 
{ 
     //to be defined 
} 
  
ushort  ip_checksum( ushort  * base ,  int   len ) 
{ 
     int  nleft =  len ; 
     int  sum = 0; 
     u_short  *w =  base ; 
     u_short  answer = 0; 
     while  (nleft>1) 
    { 
        sum += *w; 
        w++; 
        nleft -= 2; 
    } 
  
     if  (nleft == 1) 
    { 
        *( ushort  *)(&answer) = *( uchar  *)w; 
        sum += answer; 
    } 
    sum = (sum >> 16) + (sum & 0xFFFF); 
    sum += (sum >> 16); 
    answer = ~sum; 
     return  (answer); 
} 
접기 
IPv4 프로토콜을 분석하여 출력하는 ViewIP 부분을 추가합시다.
IPv4 프로토콜 스택에서는 2바이트 단위 영역들의 체크섬을 통해 유효한 패킷임을 증명하는 필드가 있어요. 이를 위한 함수를 만들기로 해요.
ushort  ip_checksum( ushort  *base,  int  len); 
void  ViewIP( char  * buf ) 
{ 
     IN_ADDR  addr; 
     iphdr  *ip = ( iphdr  *) buf ;  // 패킷 버퍼를 ethernet  구조체 포인터로 형변환 
    printf( "\n=========== IPv4 Header ==============\n" ); 
먼저 발신지와 목적지 IPv4 주소를 출력하세요. 
    addr. s_addr  = ip->src_address;     
    printf( "src:%s, " , inet_ntoa(addr)); 
  
    addr. s_addr  = ip->dst_address; 
    printf( "dst:%s\n" , inet_ntoa(addr)); 
  
헤더 길이는 단위가 4바이트입니다. 헤더 길이 필드의 값에 4를 곱하여 바이트 단위로 변환하여 출력합시다. 
    printf( "header length:%d bytes, " , ip->hlen * 4); 
버전 정보, 전체 길이, 패킷 id를 출력하세요. 
    printf( "version:%d, " , ip->version); 
    printf( "total length:%d bytes\n" , ntohs(ip->tlen)); 
    printf( "id:%d, " , ntohs(ip->id)); 
단편화 정보는 먼저 단편화 하지 말아야 한다는 DF 값을 먼저 확인하여 출력합시다. 
     if  ( DONT_FRAG (ip->frag)) 
    { 
        printf( "Don't Fragment\n" ); 
    } 
     else 
    { 
DF 값이 1일 때는 MF 값으로 같은 패킷 ID의 마지막 패킷인지 출력하세요. MF 값이 1이면 마지막 조각이 아니라는 의미이며 0이면 마지막 조각이라는 의미예요. 물론 단편화하지 않은 패킷도 MF값은 0입니다. 
         if  ( MORE_FRAG (ip->frag) == 0) 
        { 
            printf( "last fragment, " ); 
        } 
단편화 offset도 출력하세요. 
        printf( "offset:%d " ,  FRAG_OFFSET (ip->frag)); 
    } 
체크섬을 계산하여 체크섬이 정확한지 오류가 있는지 출력하세요. 
     if  (ip_checksum(( ushort  *) buf ,  sizeof ( iphdr )) == 0) 
    { 
        printf( "checksum is correct, " ); 
    } 
     else 
    { 
        printf( "checksum is not correct, " ); 
    } 
ttl값과 프로토콜을 출력하세요.  
    printf( "TTL:%d\n" , ip->ttl); 
     switch  (ip->protocol) 
    { 
     case  1: printf( "ICMP\n" );  break ; 
     case  2: printf( "IGMP\n" );  break ; 
     case  6: printf( "TCP\n" );  break ; 
     case  17: printf( "UDP\n" );  break ; 
     case  89: printf( "OSPF\n" );  break ; 
     default : printf( "Not support\n" );  break ; 
    } 
} 
void  ViewARP( char  * buf ) 
{ 
     //to be defined 
} 
체크섬 계산은 다음과 같습니다. 
ushort  ip_checksum( ushort  * base ,  int   len ) 
{ 
     int  nleft =  len ; 
     int  sum = 0; 
     u_short  *w =  base ; 
     u_short  answer = 0; 
     while  (nleft>1) 
    { 
        sum += *w; 
        w++; 
        nleft -= 2; 
    } 
  
     if  (nleft == 1) 
    { 
        *( ushort  *)(&answer) = *( uchar  *)w; 
        sum += answer; 
    } 
    sum = (sum >> 16) + (sum & 0xFFFF); 
    sum += (sum >> 16); 
    answer = ~sum; 
     return  (answer); 
} 
이제 실행해 보시고 잘못 작성한 부분은 수정하세요. 
그럼 다음에 봐요. 
모두 행복한 하루~ 
실습에 사용할 pcap 파일(다른 pcap 파일을 사용하셔도 무관합니다.) 
 demo.pcap
헤더 파일 및 소스 파일 
 ehpacket.h
 Program.c