libpcap 使用範例

最近因為工作上的需求,所以透過 libpcap 寫了一個小程式。程式紀錄在下面。應該不難懂,所以就不多做說明。程式主要參考sniffex,不過主要針對  DHCP 的封包進行攔截以及顯示。


#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 

#define SIZE_ETHERNET (14)
#define SIZE_UDP (8)
#define ETHER_ADDR_LEN (6)

const char *DHCP_MSG_TYPE_STR[9] =
{
    "None",
    "DHCP Discover",
    "DHCP Offer",
    "DHCP Request",
    "DHCP Decline",
    "DHCP ACK",
    "DHCP NACK",
    "DHCP Release",
    "DHCP Inform"
};

struct sniff_ethernet
{
    uint8_t  ether_dhost[ETHER_ADDR_LEN];    /* destination host address */
    uint8_t  ether_shost[ETHER_ADDR_LEN];    /* source host address */
    uint16_t ether_type;                     /* IP? ARP? RARP? etc */
};

struct sniff_ip
{
    uint8_t  ip_vhl;                 /* version << 4 | header length >> 2 */
    uint8_t  ip_tos;                 /* type of service */
    uint16_t ip_len;                 /* total length */
    uint16_t ip_id;                  /* identification */
    uint16_t ip_off;                 /* fragment offset field */
    #define IP_RF 0x8000             /* reserved fragment flag */
    #define IP_DF 0x4000             /* dont fragment flag */
    #define IP_MF 0x2000             /* more fragments flag */
    #define IP_OFFMASK 0x1fff        /* mask for fragmenting bits */
    uint8_t  ip_ttl;                 /* time to live */
    uint8_t  ip_p;                   /* protocol */
    uint16_t ip_sum;                 /* checksum */
    struct  in_addr ip_src,ip_dst;   /* source and dest address */
};
#define IP_HL(ip)               ( ( ( ip ) -> ip_vhl ) & 0x0f )
#define IP_V(ip)                ( ( ( ip ) -> ip_vhl ) >> 4 )

struct sniff_udp
{
    uint16_t udp_sport;             /* source port */
    uint16_t udp_dport;             /* destination port */
    uint16_t udp_len;               /* source port */
    uint16_t udp_sum;               /* source port */
};

struct sniff_dhcp
{
    uint8_t     dhcp_op;
    uint8_t     dhcp_htype;     // 1: ethernet
    uint8_t     dhcp_hlen;      // 6: ethernet address length
    uint8_t     dhcp_hops;
    uint32_t    dhcp_xid;
    uint16_t    dhcp_secs;
    uint16_t    dhcp_flags;
    struct  in_addr dhcp_ciaddr;    // Client IP address
    struct  in_addr dhcp_yiaddr;    // Your IP address
    struct  in_addr dhcp_siaddr;    // Server IP address
    struct  in_addr dhcp_giaddr;    // GW IP address
    uint8_t     dhcp_chaddr[16];    // Client HW address
    uint8_t     dhcp_legacy[192];   // For legacy bootps compatibility
    uint32_t    dhcp_magic_cookie;  // Magic Cookie
};

void parse_dhcp_packet( u_char *args, const struct pcap_pkthdr *header, const u_char *packet )
{
    static int count = 1;

    const struct sniff_ethernet *eth_hdr;
    const struct sniff_ip       *ip_hdr;
    const struct sniff_udp      *udp_hdr;
    const struct sniff_dhcp     *dhcp_hdr;
    const char                  *payload;

    int size_ip = 0;
    int size_dhcp_options = 0;

    uint8_t     *dhcp_options = NULL;
    uint8_t     dhcp_option_code = 0;
    uint8_t     dhcp_option_len = 0;
    char        hostname[260];          // 256 + 4

    printf( "Packet number: %d\n", count );

    eth_hdr = ( struct sniff_ethernet * ) packet;
    ip_hdr  = ( struct sniff_ip * )( packet + SIZE_ETHERNET );

    size_ip = IP_HL( ip_hdr ) * 4;

    printf( "\tIP Header length: %d bytes\n", size_ip );
    printf( "\tIP Src: %s\n", inet_ntoa( ip_hdr -> ip_src ) );
    printf( "\tIP Dst: %s\n", inet_ntoa( ip_hdr -> ip_dst ) );

    switch( ip_hdr -> ip_p )
    {
        case IPPROTO_TCP:
            printf( "\tProtocol: TCP\n" );
            break;
        case IPPROTO_UDP:
            printf( "\tProtocol: UDP\n" );
            break;
        case IPPROTO_ICMP:
            printf( "\tProtocol: ICMP\n" );
            break;
        default:
            printf( "\tProtocol: Others\n" );
            break;
    }

    if( ip_hdr -> ip_p != IPPROTO_UDP )
    {
        printf( "\t==> Only handle DHCP\n" );
        return;
    }

    udp_hdr = ( struct sniff_udp * )( packet + SIZE_ETHERNET + size_ip );

    printf( "\tSrc port: %d\n", ntohs( udp_hdr -> udp_sport ) );
    printf( "\tDst port: %d\n", ntohs( udp_hdr -> udp_dport ) );
    printf( "\tlength: %d\n", ntohs( udp_hdr -> udp_len ) );

    dhcp_hdr = ( struct sniff_dhcp * )( packet + SIZE_ETHERNET + size_ip + SIZE_UDP );

    printf( "\tDHCP OP: %02X\n", dhcp_hdr -> dhcp_op );
    printf( "\tDHCP HTYPE: %02X\n", dhcp_hdr -> dhcp_htype );
    printf( "\tDHCP HLEN: %02X\n", dhcp_hdr -> dhcp_hlen );
    printf( "\tDHCP HOPS: %02X\n", dhcp_hdr -> dhcp_hops );
    printf( "\tDHCP XID: %08X\n", dhcp_hdr -> dhcp_xid );
    printf( "\tDHCP SECS: %04X\n", dhcp_hdr -> dhcp_secs );
    printf( "\tDHCP FLAGS: %04X\n", dhcp_hdr -> dhcp_flags );
    printf( "\tDHCP CIADDR: %s\n", inet_ntoa( dhcp_hdr -> dhcp_ciaddr ) );
    printf( "\tDHCP YIADDR: %s\n", inet_ntoa( dhcp_hdr -> dhcp_yiaddr ) );
    printf( "\tDHCP SIADDR: %s\n", inet_ntoa( dhcp_hdr -> dhcp_siaddr ) );
    printf( "\tDHCP GIADDR: %s\n", inet_ntoa( dhcp_hdr -> dhcp_giaddr ) );
    printf( "\tDHCP CHADDR: %02X:%02X:%02X:%02X:%02X:%02X\n",
            dhcp_hdr -> dhcp_chaddr[0], dhcp_hdr -> dhcp_chaddr[1], dhcp_hdr -> dhcp_chaddr[2],
            dhcp_hdr -> dhcp_chaddr[3], dhcp_hdr -> dhcp_chaddr[4], dhcp_hdr -> dhcp_chaddr[5] );
    printf( "\tDHCP Magic Cookie: %08X\n", dhcp_hdr -> dhcp_magic_cookie );

    size_dhcp_options = ntohs( udp_hdr -> udp_len ) - sizeof( struct sniff_dhcp );
    dhcp_options = ( uint8_t * )( packet + SIZE_ETHERNET + size_ip + SIZE_UDP + sizeof( struct sniff_dhcp ) );

    while( size_dhcp_options > 0 )
    {
        dhcp_option_code = dhcp_options[0];

        if( dhcp_option_code == 0 ) // Padding, no length
        {
            size_dhcp_options--;
            dhcp_options++;
            continue;
        }

        dhcp_option_len = dhcp_options[1];

        printf( "\t\tDHCP Option (%d)", dhcp_option_code );
        switch( dhcp_option_code )
        {
            case 53:        // DHCP Message Type
                printf( ": DHCP Message Type\n" );
                printf( "\t\t\tLength: %d\n", dhcp_option_len );
                printf( "\t\t\tType: %s\n", DHCP_MSG_TYPE_STR[ dhcp_options[2] ] );
                break;
            case 50:        // Requested IP address
                printf( ": Requested IP Address\n" );
                printf( "\t\t\tLength: %d\n", dhcp_option_len );
                printf( "\t\t\tIP: %s\n", inet_ntoa( *( struct  in_addr * )( dhcp_options + 2 ) ) );
                break;
            case 12:        // Host name
                printf( ": Host Name\n" );
                printf( "\t\t\tLength: %d\n", dhcp_option_len );
                memset( hostname, 0, 260 );
                memcpy( hostname, dhcp_options + 2, dhcp_option_len );
                printf( "\t\t\tHost Name: %s\n", hostname );
                break;
            case 255:       // End
                printf( "\n\t\t\tLength: 0\n" );
                dhcp_option_len = -1;
                break;
            default:
                printf( "\n\t\t\tLength: %d\n", dhcp_option_len );
                break;
        }

        dhcp_options = dhcp_options + dhcp_option_len + 2;
        size_dhcp_options = size_dhcp_options - ( dhcp_option_len + 2 );
    }

    count++;

    return;
}

int main( int argc, char **argv )
{
    char *dev = "eth0";
    char errbuf[PCAP_ERRBUF_SIZE];

    pcap_t  *handle = NULL;
    char filter_exp[] = "port bootps";
    struct bpf_program fp;
    bpf_u_int32 mask;
    bpf_u_int32 net;

    printf( "%s: this process is a DHCP sniffer example.\n", argv[0] );

    if( argc == 1 )
    {
        // eth0
    }
    else if( argc == 2 )
    {
        dev = argv[1];
    }
    else
    {
        printf( "Usage:\n" );
        printf( "%s [iface]\n", argv[0] );
        printf( "ps: If no interface is given, default interface is eth0.\n" );
    }

    if( pcap_lookupnet( dev, &net, &mask, errbuf ) == -1 )
    {
        printf( "Cannot get IP and netmask for device %s.\n", dev );
        net = 0;
        mask = 0;
    }

    // Open the capture device.
    // 1: promiscuous mode
    // -1: never timeout
    handle = pcap_open_live( dev, 1518, 1, 1000, errbuf );

    if( handle == NULL )
    {
        printf( "Cannot open device %s.\n", dev );
        return -1;
    }

    // Compile the filter
    if( pcap_compile( handle, &fp, filter_exp, 0, net ) == -1 )
    {
        printf( "Cannot parse the filter: \n%s --> %s\n", filter_exp, pcap_geterr( handle ) );
        return -1;
    }

    // Apply filter
    if( pcap_setfilter( handle, &fp ) == -1 )
    {
        printf( "Cannot install the filter: \n%s --> %s\n", filter_exp, pcap_geterr( handle ) );
        return -1;
    }

    // Start capture
    pcap_loop( handle, -1, parse_dhcp_packet, NULL );

    return 0;
}

不過討厭的地方在於,雖然 libpcap 有提供
pcap_get_selectable_fd ,但跟 select 或是 epoll 似乎不太容易結合使用,詳情請洽 manual。

留言

  1. 作者已經移除這則留言。

    回覆刪除
  2. 作者已經移除這則留言。

    回覆刪除
  3. Hi, 我是哲緯, 好久不見!

    剛好在查 libcap 看到你這篇文章,好奇問一下你的 BPF Filter 設定的 expression

    是不是只撈單一方向的 DHCP packets?

    因為 char filter_exp[] = "port bootps";

    我查了一下 "port bootps" 可以撈到 DHCPOFFER, DHCPACK 但是 DHCPDISCOVER, DHCPREQUEST 好像會被過濾掉,除非 Filter 下 "port bootps or port bootpc"

    thanks

    回覆刪除
    回覆
    1. Hello 好久不見,最近還好嗎?

      我剛剛實驗了一下,Request 和 ACK 都可以撈到。
      當初這樣寫的原因在於 DHCP 所用的 port 是 67(bootps) 和 68 (bootpc)
      我在 filter_exp 那邊並沒有指定 src port 或是 dst port
      所以只要有一端的 port 滿足應該就可以了

      刪除
    2. Blogger的留言好像系統怪怪的,我最近在教育部服替代役

      我想我大概瞭解了,因為 port bootps 的 filter expression 並沒有限制是source port或dest. port 所以兩個方向的封包,不管是以src=67 或是 dst=67都會撈到!

      不過如果filter expression 下 "bootps" 應該會比較好

      刪除

張貼留言

這個網誌中的熱門文章

如何將Linux打造成OpenFlow Switch:Openvswitch

我弟家的新居感恩禮拜分享:善頌善禱

Linux Virtual Interface: TUN/TAP