解析数据包

现在经过上几节的学习能够进行数据报的捕获和过滤了,我们想用一个简单的"real world"程序将我们所学的知识应用于实际。

这一节里我们将利用以前的代码建立一个更实用的程序。该程序的主要目的是显示所捕获的数据报的协议头是如何解析和解释的。这个程序名叫UDPdump,它将在屏幕上显示出我们网络上UDP数据的信息。

在此我们选择解析UDP是因为它比TCP简单更加的直观明了,容易理解。下面让我们来看看源代码。

/*
 * Copyright (c) 1999 - 2005 NetGroup, Politecnico di Torino (Italy)
 * Copyright (c) 2005 - 2006 CACE Technologies, Davis (California)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the Politecnico di Torino, CACE Technologies 
 * nor the names of its contributors may be used to endorse or promote 
 * products derived from this software without specific prior written 
 * permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include "pcap.h"

/* 4字节IP地址 */
typedef struct ip_address{
    u_char byte1;
    u_char byte2;
    u_char byte3;
    u_char byte4;
}ip_address;

/* IPv4的IP头定义 */
typedef struct ip_header{
    u_char  ver_ihl;   	// 4 bit的版本信息 + 4 bits的头长
u_char  tos;        	// 服务类型
    u_short tlen;        	// 总长度
    u_short identification; 	// 标识序列
    u_short flags_fo;      	// Flags (3 bits) + Fragment offset (13 bits)
    u_char  ttl;          	// 生存期
    u_char  proto;      	// 协议
    u_short crc;         	// 校验头
    ip_address  saddr; 	// 源地址
    ip_address  daddr;   	// 目标地址
    u_int   op_pad;     	// Option + Padding
}ip_header;

/* UDP 头定义 */
typedef struct udp_header{
    u_short sport;          // 源端口
    u_short dport;          // 目标端口
    u_short len;            // 数据报长度
    u_short crc;            // 校验和
}udp_header;

/* 定义处理包的函数 */
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);


int main()
{
pcap_if_t *alldevs;
pcap_if_t *d;
int inum;
int i=0;
pcap_t *adhandle;
char errbuf[PCAP_ERRBUF_SIZE];
u_int netmask;
char packet_filter[] = "ip and udp";
struct bpf_program fcode;

    /* Retrieve the device list */
    if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
    {
        fprintf(stderr,"Error in pcap_findalldevs: %s/n", errbuf);
        exit(1);
    }
    
    /* Print the list */
    for(d=alldevs; d; d=d->next)
    {
        printf("%d. %s", ++i, d->name);
        if (d->description)
            printf(" (%s)/n", d->description);
        else
            printf(" (No description available)/n");
    }

    if(i==0)
    {
        printf("/nNo interfaces found! Make sure WinPcap is installed./n");
        return -1;
    }
    
    printf("Enter the interface number (1-%d):",i);
    scanf_s("%d", &inum);
    
    if(inum < 1 || inum > i)
    {
        printf("/nInterface number out of range./n");
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }

    /* Jump to the selected adapter */
    for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
    
    /* Open the adapter */
    if ( (adhandle= pcap_open(d->name,  // name of the device
                             65536,     // portion of the packet to capture. 
                                        // 65536 grants that the whole packet will be captured on all the MACs.
                             PCAP_OPENFLAG_PROMISCUOUS,         // promiscuous mode
                             1000,      // read timeout
                             NULL,      // remote authentication
                             errbuf     // error buffer
                             ) ) == NULL)
    {
        fprintf(stderr,"/nUnable to open the adapter. %s is not supported by WinPcap/n");
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }
    
    /* Check the link layer. We support only Ethernet for simplicity. */
    if(pcap_datalink(adhandle) != DLT_EN10MB)
    {
        fprintf(stderr,"/nThis program works only on Ethernet networks./n");
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }
    
    if(d->addresses != NULL)
        /* Retrieve the mask of the first address of the interface */
        netmask=((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
    else
        /* If the interface is without addresses we suppose to be in a C class network */
        netmask=0xffffff; 


    /*compile the filter*/
    if (pcap_compile(adhandle, &fcode, packet_filter, 1, netmask) <0 )
    {
        fprintf(stderr,"/nUnable to compile the packet filter. Check the syntax./n");
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }
    
    /*set the filter*/
    if (pcap_setfilter(adhandle, &fcode)<0)
    {
        fprintf(stderr,"/nError setting the filter./n");
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }
    
    printf("/nlistening on %s.../n", d->description);
    
    /* At this point, we don't need any more the device list. Free it */
    pcap_freealldevs(alldevs);
    
    /* start the capture */
    pcap_loop(adhandle, 0, packet_handler, NULL);
    
    return 0;
}

/* Callback function invoked by libpcap for every incoming packet */
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data)
{
    struct tm ltime;
    char timestr[16];
    ip_header *ih;
    udp_header *uh;
    u_int ip_len;
    u_short sport,dport;
    time_t local_tv_sec;

    /*
     * Unused variable
     */
    (VOID)(param);

    /* convert the timestamp to readable format */
    local_tv_sec = header->ts.tv_sec;
    localtime_s(<ime, &local_tv_sec);
    strftime( timestr, sizeof timestr, "%H:%M:%S", <ime);

    /* print timestamp and length of the packet */
    printf("%s.%.6d len:%d ", timestr, header->ts.tv_usec, header->len);

    /* retireve the position of the ip header */
    ih = (ip_header *) (pkt_data +
        14); //length of ethernet header

    /* retireve the position of the udp header */
    ip_len = (ih->ver_ihl & 0xf) * 4;
    uh = (udp_header *) ((u_char*)ih + ip_len);

    /* convert from network byte order to host byte order */
    sport = ntohs( uh->sport );
    dport = ntohs( uh->dport );

    /* print ip addresses and udp ports */
    printf("%d.%d.%d.%d.%d -> %d.%d.%d.%d.%d/n",
        ih->saddr.byte1,
        ih->saddr.byte2,
        ih->saddr.byte3,
        ih->saddr.byte4,
        sport,
        ih->daddr.byte1,
        ih->daddr.byte2,
        ih->daddr.byte3,
        ih->daddr.byte4,
        dport);
}

首先我们设置UDP过滤器为“ip and udp”,用这种方法我们确保packet_handler()只接受到基于IPV4的UDP数据。这样我们简化了解析并且提高了程序的效率。

我们同样定义了两个数据结构来描述IP 和UDP的头部信息,packet_handler()用这两个结构来定位头部的各种字段。

packet_handler()虽然只是限于处理单一的协议解析器(IPv4里的UDP)但却显示了复杂的嗅探器如tcpdump/WinDump的工作原理。既然我们对MAC地址的头部并不感兴趣所以我们跳过它。为了简单,我们在开始捕获之前用pcap_datalink()来检查MAC层,确保我们的操作在Ethernet networks上,再次我们确保MAC头为14 bytes。

MAC头之后是IP头,我们从中提取出源地址和目的地址。

IP之后是UDP,在确定UDP的位置时有点复杂,因为IP头的长度不是定长。因此我们用头长字段来定位UDP,一旦我们确定了UDP的起始位置,我们就可以解析出原端口和目的端口。

附原文:

Now that we are able to capture and filter network traffic, we want to put our knowledge to work with a simple "real world" application.

In this lesson we will take code from the previous lessons and use these pieces to build a more useful program. the main purpose of the current program is to show how the protocol headers of a captured packet can be parsed and interpreted. The resulting application, called UDPdump, prints a summary of the UDP traffic on our network.

We have chosen to parse and display the UDP protocol because it is more accessible than other protocols such as TCP and consequently is an excellent initial example. Let's look at the code:

/* codes */

First of all, we set the filter to "ip and udp". In this way we are sure that packet_handler() will receive only UDP packets over IPv4: this simplifies the parsing and increases the efficiency of the program.

We have also created a couple of structs that describe the IP and UDP headers. These structs are used by packet_handler() to properly locate the various header fields.

packet_handler(), although limited to a single protocol dissector (UDP over IPv4), shows how complex "sniffers" like tcpdump/WinDump decode the network traffic. Since we aren't interested in the MAC header, we skip it. For simplicity and before starting the capture, we check the MAC layer with pcap_datalink() to make sure that we are dealing with an Ethernet network. This way we can be sure that the MAC header is exactly 14 bytes.

The IP header is located just after the MAC header. We will extract the IP source and destination addresses from the IP header.

Reaching the UDP header is a bit more complicated, because the IP header doesn't have a fixed length. Therefore, we use the IP header's length field to know its size. Once we know the location of the UDP header, we extract the source and destination ports.

The extracted values are printed on the screen, and the result is something like:

1. /Device/Packet_{A7FD048A-5D4B-478E-B3C1-34401AC3B72F} (Xircom t 10/100 Adapter)

Enter the interface number (1-2):1

listening on Xircom CardBus Ethernet 10/100 Adapter...

16:13:15.312784 len:87 130.192.31.67.2682 -> 130.192.3.21.53

16:13:15.314796 len:137 130.192.3.21.53 -> 130.192.31.67.2682

16:13:15.322101 len:78 130.192.31.67.2683 -> 130.192.3.21.53

Each of the final 3 lines represents a different packet.

#end