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

getting ip address of a packet in pcap file

本文关键字:IP 地址 数据包 PCAP 文件 获取      更新时间:2023-10-16

我正在使用"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 地址相同!!它打印的源和目标端口不正确!!问题出在哪里?我该怎么办?请帮助我。

源端口和目标端口按网络字节顺序排列(大端序)。使用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 数据包。因此,您可能需要检查要6(TCP) 的 IP 标头协议字段的值,如上面的代码所示。

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

结构填料应该没问题。

首先,确保数据包实际上具有以太网标头(不要假设它们有!),方法是

if (pcap_datalink(handler) != DLT_EN10MB) {
fprintf(stderr, "%s is not an Ethernet capturen",
{whatever the pathname of the file is});
exit(2);
}

打开捕获文件后。

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

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;

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

如果你对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)。

inet_ntoa() 返回静态缓冲区中的点和数字字符串,每次调用函数都会覆盖该字符串。

  • http://www.retran.com/beej/inet_ntoaman.html

//我们可以像这样打印源和目标的 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