근래에 계속 하고 있는 프로젝트에서 결국 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..... ㅠ.ㅠ
|