在pcap文件中获取数据包的IP地址

时间:2014-01-19 20:42:07

标签: c++ winpcap

我用'winpcap'编程,我在我的程序中读了一个“.pcap”文件,之后我想得到数据包的IP地址,我写了这些代码来获取ip地址,这是一块我的代码:

  struct sniff_ip {
        u_char ip_vhl;      /* version << 4 | header length >> 2 */
        u_char ip_tos;      /* type of service */
        u_short ip_len;     /* total length */
        u_short ip_id;      /* identification */
        u_short 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 */
        u_char ip_ttl;      /* time to live */
        u_char ip_p;        /* protocol */
        u_short ip_sum;     /* checksum */
        struct in_addr ip_src;
        struct in_addr ip_dst; /* source and dest address */



      struct sniff_tcp {
                u_short th_sport;   /* source port */
                u_short th_dport;   /* destination port */
                u_int32_t th_seq;       /* sequence number */
                u_int32_t th_ack;       /* acknowledgement number */};

然后我读了这个文件:

while (pcap_next_ex(handler, &header, &packet) >= 0)
    {

        ip = (struct sniff_ip*)(packet + SIZE_ETHERNET);
        tcp = (struct sniff_tcp*)(packet + SIZE_ETHERNET + size_ip);

        printf("src port: %d dest port: %d \n", tcp->th_sport, tcp->th_dport);
        fprintf(fp,"src port: %d dest port: %d \n", tcp->th_sport, tcp->th_dport);

        printf("src address: %s dest address: %s \n",  inet_ntoa(ip->ip_src),  inet_ntoa(ip->ip_dst));
        fprintf(fp,"src address: %s dest address: %s \n",  inet_ntoa(ip->ip_src),  inet_ntoa(ip->ip_dst));

        printf("seq number: %u ack number: %u \n", (unsigned int)tcp-> th_seq, (unsigned int)tcp->th_ack);
        fprintf(fp,"seq number: %u ack number: %u \n", (unsigned int)tcp-> th_seq, (unsigned int)tcp->th_ack);

但源和IP地址是相同的!!!它打印源和目标端口不正确!!问题是什么?我该怎么办?plz帮助我。

4 个答案:

答案 0 :(得分:4)

源端口和目标端口采用网络字节顺序(big-endian)。使用ntohs以正确的机器字节顺序获取它们。对于SEQ和ACK也是如此,对那些使用ntohl。 IP标头的大小可能不总是20,将ip_hdr_len的值乘以4以获得实际大小。

如果您的编译器支持位域,您可以将它们用于IP头声明,以简化操作:

struct sniff_ip {
    u_char ip_hdr_len:4;
    u_char ip_ver:4;

固定代码:

while (pcap_next_ex(handler, &header, &packet) >= 0) {      
    ip = (struct sniff_ip*)(packet + SIZE_ETHERNET);

    if (ip->ip_p == 6 /* tcp protocol number */) {
        tcp = (struct sniff_tcp*)(packet + SIZE_ETHERNET + ip->ip_hdr_len * 4);

        u_short srcport = ntohs(tcp->th_sport);
        u_short dstport = ntohs(tcp->th_dport);
        printf("src port: %d dest port: %d \n", srcport, dstport);

        char srcname[100];
        strcpy(srcname, inet_ntoa(ip->ip_src));
        char dstname[100];
        strcpy(dstname, inet_ntoa(ip->ip_dst));
        printf("src address: %s dest address: %s \n", srcname, dstname);

        u_long seq = ntohl(tcp->th_seq);
        u_long ack = ntohl(tcp->th_ack);
        printf("seq number: %u ack number: %u \n", seq, ack);
    }       
}

请注意,并非每个数据包都包含TCP数据,除非您使用pcap_compilepcap_setfilter应用了过滤器,或者可以确保您正在读取的文件仅包含TCP数据包。因此,您可能需要检查IP标头协议字段的值为6(TCP),如上面的代码所示。

另请注意,Wireshark默认显示相对的SEQ和ACK编号,因此它们与您在那里看到的不匹配。

结构包装应该没问题。

答案 1 :(得分:2)

首先,通过执行

,确保数据包确实具有以太网标头(不要不要假设它们有!)
if (pcap_datalink(handler) != DLT_EN10MB) {
    fprintf(stderr, "%s is not an Ethernet capture\n",
            {whatever the pathname of the file is});
    exit(2);
}

在您打开捕获文件之后。

然后,您必须确保数据包确实一个 IPv4 数据包,方法是将字符串 "ip" 编译为过滤器,使用 pcap_compile(),并将其应用于通过在开始阅读之前使用 pcap_setfilter() 和编译的过滤器调用 handler 文件,或在阅读循环中检查它:

while (pcap_next_ex(handler, &header, &packet) >= 0) {
    u_short ethertype;
    
    /*
     * For an Ethernet packet, the destination Ethernet
     * address is in bytes 0 through 5, the source Ethernet
     * address is in bytes 6 through 11, and the type/length
     * field is in bytes 12 and 13.
     *
     * It's a big-endian value, so fetch the first byte, at
     * an offset of 12, and put it in the upper 8 bits of
     * the value, and then fetch the second byte, at an offset
     * of 13, and put it in the lower 8 bits of the value.
     */
    ethertype = (packet[12] << 8) | packet[13];
    
    /*
     * Now make sure it's the Ethernet type value for
     * IPv4, which is 0x0800.
     */
    if (ethertype != 0x0800) {
        /*
         * Skip this packet.
         */
        continue;
    }

    /*
     * Now we know it's an IPv4 packet, so process it as such.
     */
    ip = (struct sniff_ip*)(packet + SIZE_ETHERNET);

注意:不要不要对版本和头长度字段使用位字段,因为不能保证它们的顺序正确。相反,使用 ip_vhl 计算标头长度,例如

    size_ip = (ip->ip_vhl & 0x0F) * 4;

答案 2 :(得分:0)

如何打包结构取决于你的编译器;例如,gcc使用#pragma pack()。适用于pack pragma gccpack pragma visual c或您正在使用的任何编译器的Google。

如果你在printf上调用它两次,你需要保存inet_ntoa的结果,比如

char *srcname=strdup(inet_ntoa(ip->ip_src));
char *dstname=strdup(inet_ntoa(ip->ip_dst));
printf("src address: %s dest address: %s \n", srcname, dstname);
free(srcname);
free(dstname);

(注意,您应该添加错误检查,函数结果指针可能为NULL)。

答案 3 :(得分:0)

// 我们可以像这样打印源和目标的 IP 地址。

while (currpos < InLen){

    pcktcnt++;
    currpos = ftello64(InRaw);
    if (fread((char *) &pckthdr, sizeof(pckthdr), 1, InRaw) != 1) {
        break;
    }

    if (fread((char *) &pcktbuf, pckthdr.caplen, 1, InRaw) != 1) {
            break;
    }

    /* Find stream in file, count packets and get size (in bytes) */
    if( isgetTCPIP(pcktbuf, &size_ip, &size_tcp,fp)){
        /* Simple example code */       
        char srcIp[INET_ADDRSTRLEN];    /*or malloc it later*/
        char dstIp[INET_ADDRSTRLEN];

        inet_ntop(AF_INET, &(ip->ip_src), srcIp,  INET_ADDRSTRLEN);
        inet_ntop(AF_INET, &(ip->ip_dst), dstIp,  INET_ADDRSTRLEN);
        fprintf("packet: %d, Src addr.: %s, Src port #: %d, Dest. addr: %s,  Dest. port #: %d,   \n",pcktcnt, srcIp,  tcp->th_sport, dstIp, tcp->th_dport);
    }  // isgetTCPIP