libpcap 예제 기본 사용법


libpcap 예제 기본 사용법
패킷 캡쳐 라이브러리인 libpcap을 설치하고 사용법과 예제 프로그래밍 방법 설명드립니다.
libpcap은 tcpdump 뿐만 아니라 Wireshark/TShark 등에서 네트워크 패킷을 캡쳐하기 위해서 사용되는 라이브러리 입니다.
리눅스 환경을 기준으로 설명드리지만 윈도우 환경일 경우 winpcap 이라는 라이브러리를 이용하여 동일하게 테스트 하실 수 있습니다.

libpcap 예제 기본 사용법

우분투 환경일 경우 아래의 명령어로 패키지를 설치 할 수 있습니다.

$ sudo apt-get install libpcap-dev

그리고 예제에 사용된 전체 소스코드는 아래의 명령어로 다운 받으실 수 있습니다.

$ git clone https://hiseon.me/reps/libpcap-tutorial.git

pcap 파일 구조에 대해 확인 하는 내용은 아래의 페이지를 참고하시기 바랍니다.

네트워크 인터페이스 정보

먼저, 시스템에 설치된 네트워크 인터페이스 정보를 확인 하는 예제입니다.

#include <stdio.h>
#include <pcap.h>

int main(void) {
    pcap_if_t *alldevs;
    pcap_if_t *d;
    int i = 0;

    char errbuf[PCAP_ERRBUF_SIZE];

    if (pcap_findalldevs(&alldevs, errbuf) < 0) {
        printf("pcap_findalldevs error\n");
        return 1;
    }

    if (!alldevs) {
        printf("%s\n", errbuf);
    }

    for (d=alldevs; d; d=d->next) {
        printf("%p : %d. %s", d, ++i, d->name);

        if (d->description) printf(" (%s)", d->description);
        printf("\n");
    }

    pcap_freealldevs(alldevs);

    return 0;
}

위의 예제를 빌드하기 위해서는 -lpcap 이라는 링크 옵션을 추가해야 하는데, 아래의 명령어로 빌드 할 수 있십니다.

gcc -o example example.c -lpcap

시스템에 설치된 네트워크 인터페이스 정보를 확인 하기 위해서는 pcap_findalldevs 함수를 사용하면 됩니다.
pcap_findalldevs 함수를 사용하였을 경우 메모리를 동적으로 할당 하기 때문에 함수 호출 뒤에는 pcap_freealldevs 함수를 호출해 주셔야 합니다.

int pcap_findalldevs(pcap_if_t **, char *);
void  pcap_freealldevs(pcap_if_t *);

pcap_if_t 구조체에 네트워크 인터페이스 정보가 저장이 되게 되는데, d->addresses 의 필드를 참고하여 네트워크 인터페이스에 할당된 주소 정보를 추가로 확인 할 수 있습니다. 아래의 내용은 pcap_if_t 구조체의 내용입니다.

/*
 * Item in a list of interfaces.
 */
struct pcap_if {
  struct pcap_if *next;
  char *name;   /* name to hand to "pcap_open_live()" */
  char *description;  /* textual description of interface, or NULL */
  struct pcap_addr *addresses;
  bpf_u_int32 flags;  /* PCAP_IF_ interface flags */
};

typedef struct pcap_if pcap_if_t;

보다 자세한 내용은 git 소스코드를 참고해 주시거나, pcap.h 파일의 내용을 참고하시면 됩니다.
참고로 pcap.h 파일은 아래 위치에 존재하게 됩니다.

/usr/include/pcap/pcap.h

아래의 내용은 인터페이스 주소를 가져오고, 주소를 출력하는 결과 내용입니다.

1 :  enp6s0
2 :  ppp0
3 :  any
4 :  lo
5 :  docker0
6 :  nflog
7 :  nfqueue
8 :  usbmon1
9 :  usbmon2
10 :  usbmon3
11 :  usbmon4
12 :  usbmon5
13 :  usbmon6
14 :  usbmon7
15 :  usbmon8
number : 1
name : enp6s0
address : 192.168.0.66

네트워크 패킷 캡쳐

아래의 내용은 전체 네트워크 인테페이스 정보를 가져오고, 패킷을 수신하는 예제입니다.

#include <stdio.h>
#include <string.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <pcap.h>

void packet_handler(u_char *param,
  const struct pcap_pkthdr *header, const u_char *pkt_data) {
  printf("caplen : %d\n", header->caplen);
  printf("len : %d\n", header->len);
}

int main(int argc, char **argv) {
    pcap_t *adhandle;
    char errbuf[PCAP_ERRBUF_SIZE];
    pcap_if_t *alldevs;
    pcap_if_t *d;
    struct pcap_addr *a;
    int i = 0;
    int no;

    if (pcap_findalldevs(&alldevs, errbuf) < 0) {
        printf("pcap_findalldevs error\n");
        return 1;
    }

    for (d=alldevs; d; d=d->next) {
        printf("%d :  %s\n", ++i, (d->description)?(d->description):(d->name));
    }

    printf("number : ");
    scanf("%d", &no);

    if (!(no > 0 && no <= i)) {
        printf("number error\n");
        return 1;
    }

    for (d=alldevs, i=0; d; d=d->next) {
        if (no == ++i)  break;
    }

    if (!(adhandle= pcap_open_live(d->name, 65536, 1, 1000, errbuf))) {
        printf("pcap_open_live error %s\n", d->name);
        pcap_freealldevs(alldevs);
        return -1;
    }

    pcap_freealldevs(alldevs);

    pcap_loop(adhandle, 0, packet_handler, NULL);

    pcap_close(adhandle);

    return 0;
}

위의 예제에서 가장 중요한 함수가 있는데 바로 아래의 2개의 함수 입니다.

pcap_t *pcap_open_live(const char *device, int snaplen, int promisc, int to_ms, char *errbuf);
int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user);

pcap_open_live 함수는 패킷을 캡쳐하기 위해서 네트워크 디바이스를 오픈 하는 역할을 합니다.
그리고, pcap_loop 함수는 패킷이 수신되었을 경우 호출되는 핸들러 함수를 지정합니다. 핸들러 함수의 원형은 아래와 같습니다.

typedef void (*pcap_handler)(u_char *user, const struct pcap_pkthdr *h, const u_char *bytes);

함수를 통해서 pcap_pkthdr 구조체의 포인터 주소가 전달되는데, pcap_pkthdr 구조체 내용은 아래와 같습니다.

struct pcap_pkthdr {
    struct timeval ts;  /* time stamp */
    bpf_u_int32 caplen; /* length of portion present */
    bpf_u_int32 len;  /* length this packet (off wire) */
};

pcap_pkthdr 구조체 정보와, 실제 패킷의 포인터 주소값을 이용하여 수신된 패킷을 처리 할 수 있습니다.
위의 예제에서는 수신된 패킷을 단순히 크기만 출력하였지만, 앞으로 패킷 헤더를 함께 분석해 보도록 하겠습니다.

1 :  enp6s0
2 :  ppp0
3 :  Pseudo-device that captures on all interfaces
4 :  lo
5 :  docker0
6 :  Linux netfilter log (NFLOG) interface
7 :  Linux netfilter queue (NFQUEUE) interface
8 :  USB bus number 1
9 :  USB bus number 2
10 :  USB bus number 3
11 :  USB bus number 4
12 :  USB bus number 5
13 :  USB bus number 6
14 :  USB bus number 7
15 :  USB bus number 8
number : 1
caplen : 56
len : 56
caplen : 60
len : 60

프로토콜 헤더

pcap_handler 핸들러 함수에서 호출될때에 전달되는 bytes 포인터 변수의 내용을 프로토콜에 맞춰서 출력하실 수 있습니다. 현재 아래와 같은 프로토콜 종류에 대해한 글들과 예제 소스코드등이 작성되었습니다.

이더넷 프레임 헤더 구조 분석 예제

( 본문 인용시 출처를 밝혀 주시면 감사하겠습니다.)