KOEI's Diary 위치로그  |  태그  |  방명록
tcpdump 에 해당하는 글1 개
2007/03/25   libpcap linux에서 timeout 되게 패치하기 (2)


libpcap linux에서 timeout 되게 패치하기
개발이야기 | 2007/03/25 20:39
2007/03/25 20:39 2007/03/25 20:39
근래에 계속 하고 있는 프로젝트에서 결국 tcpdump를 사용하게 되었는데,
Failover 처리를 하다보니 캡쳐하다 특정 시간만큼 패킷이 없을 경우 timeout을 처리해야할 필요성이 생겼다. tcpdump 소스에서 관련 부분을 보면
- tcpdump.c -
 885         *ebuf = '\0';
 886         pd = pcap_open_live(device, snaplen, !pflag, 1000, ebuf);
 887         if (pd == NULL)
4번째 인자가 to_ms, timeout과 관련된 인자로 설정이 되어있어서, 잘 되는구나 하고 진행을 했으나, 리눅스에서는 timeout이 동작하지 않아서 소스를 들여다 봤다.
 pcap.c에서 보니 죄다 function pointer로 위임해놨군. 으움 실제 빌드를 돌려보고 실제로 컴파일되는 녀석을 찾아보니 이놈이다. pcap-linux.c 그중에 일부를 보면
- pcap-linux.c -
 236 pcap_t *
 237 pcap_open_live(const char *device, int snaplen, int promisc, int to_ms,
 238     char *ebuf)
 239 {
...
 267         /* Initialize some components of the pcap structure. */
 268
 269         memset(handle, 0, sizeof(*handle));
 270         handle->snapshot        = snaplen;
 271         handle->md.timeout      = to_ms;
...
 492     /* Receive a single packet from the kernel */
 493
 494     bp = handle->buffer + handle->offset;
 495     do {
 496         /*
 497          * Has "pcap_breakloop()" been called?
 498          */
 499         if (handle->break_loop) {
 500             /*
 501              * Yes - clear the flag that indicates that it
 502              * has, and return -2 as an indication that we
 503              * were told to break out of the loop.
 504              */
 505             handle->break_loop = 0;
 506             return -2;
 507         }
 508         fromlen = sizeof(from);
 509         packet_len = recvfrom(
 510             handle->fd, bp + offset,
 511             handle->bufsize - offset, MSG_TRUNC,
 512             (struct sockaddr *) &from, &fromlen);
 513     } while (packet_len == -1 && errno == EINTR);

 디버거를 돌릴 생각을 못하고 recvfrom 전후로 출력만을 넣어본 결과 blocking이 된다는 사실을 확인. 일단 늘 그렇듯이 당황. nonblocking으로 바꾸면.. 상상하기도 싫고..... 잠시 마음의 여유를 가지고 시간이 별로 없어서 다른 방법을 찾아보지 않고 소스를 고쳐보기로 했다.
 (얼마전에 했었던 C++로 작업된 프로젝트 삽질 이후에 또 삽질을.... 이번에는 C다. -ㅅ-)

 이런 패턴을 어디서 많이 보던건데..... 아하!
 IRC서버소스에서 sleep이 없을 때 select를 쓴다.. 이건 상관없잖아!!
 ACE에서 recv에서 timeout을 지원하지 않을 때 어떻게 하더라. multiplexer의 timeout을 이용하면. That's right~
 그래서 붙이려다가 이왕이면 select말고 다른거 한번 써보자. 해서 epoll로 살포시 붙여봤다. 작업하면서 소스보기 번거로우니 쓰지 않는 파일이나 화면에 출력결과를 보여주는 루틴등은 싸악 날려주고 -ㅅ- 오옹 보다보니 리눅스에서 어떻게 캡쳐를 하면 되는지도 나온다. 여러 인터페이스(각각 fd로 나오니까)에서 캡쳐하는걸 하나의 epoll로 처리해도 괜찮겠다는 생각이 들기 시작.. 그러나 그런게 중요한건 아니니..

#include <sys/epoll.h>

.... 중략 ....

#define  EPOLL_EVENTS_COUNT 16
static int epollfd = -1;
static struct epoll_event ev, * epoll_events = NULL;

.... 중략 ....

pcap_t *
pcap_open_live(const char *device, int snaplen, int promisc, int to_ms
    char *ebuf)
{

.... 중략 ....

    handle->selectable_fd = handle->fd;
    do {
        if ( -1 == epollfd)
            epollfd = epoll_create(EPOLL_EVENTS);
        if ( -1 == epollfd )
           break;
        if ( NULL == epoll_events )
             epoll_events = (struct epoll_event *)malloc(sizeof(*epoll_events) * EPOLL_EVENTS);
        ev.events = EPOLLIN;
        ev.data.fd = handle->selectable_fd;
        if ( -1 == epoll_ctl(epollfd, EPOLL_CTL_ADD, handle->selectable_fd, &ev) )
           break;

       handle->read_op = pcap_read_linux;
       handle->inject_op = pcap_inject_linux;
       handle->setfilter_op = pcap_setfilter_linux;
       handle->setdirection_op = pcap_setdirection_linux;
       handle->set_datalink_op = NULL; /* can't change data link type */
       handle->getnonblock_op = pcap_getnonblock_fd;
       handle->setnonblock_op = pcap_setnonblock_fd;
       handle->stats_op = pcap_stats_linux;
       handle->close_op = pcap_close_linux;

       return handle;
    } while(0);
  
    fprintf(stderr, "epoll setting error\n");
    pcap_close_linux(handle);
    free(handle);
    return NULL;
}
....
static int
pcap_read_packet(pcap_t *handle, pcap_handler callback, u_char *userdata)
{
 u_char   *bp;
 int   offset;
#ifdef HAVE_PF_PACKET_SOCKETS
 struct sockaddr_ll from;
 struct sll_header *hdrp;
#else
 struct sockaddr  from;
#endif
 socklen_t  fromlen;
 int   packet_len, caplen;
 struct pcap_pkthdr pcap_header;

 int n ; /* epoll returned count */
  
.... 중략 ....

 /* Receive a single packet from the kernel */

 bp = handle->buffer + handle->offset;
 do {
  /*
   * Has "pcap_breakloop()" been called?
   */
  if (handle->break_loop) {
   /*
    * Yes - clear the flag that indicates that it
    * has, and return -2 as an indication that we
    * were told to break out of the loop.
    */
   handle->break_loop = 0;
   return -2;
  }
  fromlen = sizeof(from);

  n = epoll_wait(epollfd, epoll_events, EPOLL_EVENTS, handle->md.timeout);
  if (0 >= n)
  {
   break ;
  }
  for ( int i = 0 ; i < n ; ++i )
  {
    if ( epoll_events[i] == handle->selectable_fd )
    {

       packet_len = recvfrom(
       handle->fd, bp + offset,
       handle->bufsize - offset, MSG_TRUNC,
      (struct sockaddr *) &from, &fromlen);
    }
 } while (packet_len == -1 && errno == EINTR);

 if ( n == 0 || (  n < 0 && errno == EINTR ) )
 {
  /* callback function must check packet_header, buffer_pointer is NULL. if they are NULL, it is timeout */
  callback(userdata, NULL, NULL);
  return 0;
 }

.... 중략 ....

static void pcap_close_linux( pcap_t *handle )
{
 struct pcap *p, *prevp;
 struct ifreq ifr;

.... 중략 ....

 if (handle->md.device != NULL)
  free(handle->md.device);
 handle->md.device = NULL;
 if (handle->selectable_fd > 0 ) /* 여긴 대충 대충 -ㅅ- */
 {
   close(epollfd);
 }
 pcap_close_common(handle);

 요렇게 간단하게 몇 부분을 고치니 timeout을 callback에서 packet_header와 capture buffer가 NULL임을 확인하는 것으로 timeout 체킹이 가능해졌다. 다만 50라인 정도 작업하는데 3시간 정도를 쓰다니 -_+
 요튼 소기의 목적을 달성하고 timeout을 이용한 다른 코드를 tcpdump에 붙이기 성공.
작업하면서 뻘짓한 것은 tcpdump 소스를 보면서 print-*.c 얘네들을 지우면서 소스를 파악할 때까지는 분위기가 좋았다. libpcap 소스를 보면서 먼저 문서화된 것을 보지 않아서 인터페이스를 파악하는데 삽질한 것(Ruby/Pcap만으로 이미 난 대부분을 알고 있어라는 자만심에)에서 시간을 많이 뺏겼다. 언제나 그렇듯이 소스보다는 문서를, 문서보다는 예제를 먼저 보자라는 생각을 빼먹는다.
 
 작업하면서 printf에서 사용하는 formatted string에서 잼있는 것을 발견. (나만 몰랐는지도)
  sprintf(buffer, "%s%0*d", orig_name, max_chars, cnt);
%*d라고 적으면 두개의 인자를 받고 앞의 인자가 *로 자리수를 받고 뒤의 인자가 값이 된다.

여튼 즐거운 working sunday..... ㅠ.ㅠ

태그 : , , ,
트랙백0 | 댓글2
이 글의 관련글(트랙백) 주소 :: http://koei.fiaa.net/trackback/488
rath 2007/04/02 03:56 L R X
오랜만에 놀러왔는데, 열정 가득한 포스팅이 ㄷㄷㄷ하게 올라왔네요~ >.<
KOEI 2007/04/03 11:14 L X
흐흐 부끄럽사와요. 래쓰횽이야 말로 요사이 무지 달리시더라는. 건강 조심하세용~
일하다가 삽질한거 정리해놓으면 나중에 볼때 좋아서연..
래쓰횽 보고 시퍼요 ㅠ.ㅠ

[로그인][오픈아이디란?]
아이디 :
비밀번호 :
홈페이지 :
  비밀글로 등록
내용 :
 



[PREV] [1] [NEXT]
관리자  |   글쓰기
BLOG main image
소소한 일상.. 그안의 나..
전체 (11)
개발이야기 (7)
전산쟁이 맹달이 (2)
사랑하는사람들 (2)
Reading (0)
AIX 삽질 나연이 조카 libpcap 데스크탑 Ubuntu 짝프로그래밍 TCP agile 개발 Feisty COBL call_graph Ruby 리눅스 VB.NET tcpdump
COBOL call flow 그려보기 (8)
Ruby 그리고 AIX (4)
libpcap linux에서 timeout... (2)
사랑하는 조카 =)
[리뷰] Ubuntu Feisty - 충분... (6)
헙 마님 3개월만에 들어와봤...
2009 - KOEI
별걸 다 해 -_-
2009 - seha
냥냥 올만이야 3달만의 리플...
2008 - KOEI
광용싸마~ 간만이에용~ㅋㅋ...
2008 - nurinamu
로오오오옹 타이이임 노오오...
2008 - KOEI
Active Directory 에 사용자...
해적의 쉼터
Total : 23843
Today : 0
Yesterday : 18
태터툴즈 배너
rss
 
 
 
위치로그 : 태그 : 방명록 : 관리자
KOEI’s Blog is powered by Tattertools.com / Designed by plyfly.net