下面是从这次编程过程中总结的小收获,总结如下,可能不难,但借此机会整理如下,供以后查看作为参考:
1)首先是各个类型的定义,这是以前自己没有注意过的,现在总结如下:
u_int与int,u_char与char,这两组类型变量在内存里分别所占的长度是一样的
u_int/int占4个字节,u_char/char占1个字节。
这些类型的使用场合:
u_int(unsigned int)表示无符号整形,意味着该变量的值不会出现负数;
int表示有符号整形,意味着该变量可以是正、负数;
u_char数组(或者指针),表示该变量是(或者指向)二进制数据,即里面可能有不可见字符;
而char数组则一般是可见字符串了。
uint8_t,uint16_t,uint32_其实这些都不是新的数据类型,它们只是使用typedef给类型起的别名,这样其实是为了程序的规范,或者是为了以后修改代码比较方便。
查看定义可以发现如下:
typedef unsigned char u_int8_t;(u_char)
typedef signed char int8_t;
typedef unsigned int u_int16_t;(u_int)
typedef signed int int16_t;
2)winpcap网络编程中有其很多特殊性,因此说一下其中的某些函数。
首先这个程序中最重要的ntohs()
解释:将一个无符号短整形数从网络字节顺序转换为主机字节顺序。
#include <winsock.h>
u_short PASCAL FAR ntohs( u_short netshort);
netshort:一个以网络字节顺序表达的16位数。
其实也就是将一个16位数由网络字节顺序转换为主机字节顺序。
好,那么问题来了,什么是网络字节顺序与主机字节顺序呢,这是自己在编程过程中一直存在的问题,后来终于看懂了:
首先介绍两种字节序类型
1. Little endian:将低序字节存储在起始地址
2. Big endian:将高序字节存储在起始地址
前者就是我们平时用的x86系列机子的顺序,及我们上学期在计算机组成原理中学的,地址低位存储值的低位 ;地址高位存储值的高位。
后者是比较直观的方式,只需要把内存地址从左到右按照由低到高的顺序写出。这就是网络字节顺序。网络字节顺序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。
为了更直观的了解,下面我从程序中截了一个屏
我把程序中nthos()这个注释掉的结果是如上所数据示的,为了方便看,我加了一个display()函数,这样对比看的比较清晰。
3)由于winpcap网络编程中IP和MAC地址的特殊性,下面继续说另一个函数:
Inet_ntoa():
将网络地址转换成“.”点隔的字符串格式
这个函数使用的过程中需要注意一下问题,这也是在写程序中的问题:
首先:其定义为char *inet_ntoa (struct in_addr)因此参数必须是struct in_addr类型的,不然是用不了。对比本程序中的IP地址,必须都得是定义为struct in_addr。
其次:由于in_addr是老函数,可能会报错
inet_pton和inet_ntop这2个函数是IP地址转换函数是比较新的函数,微软希望我们用他们新的函数,因此我们这里要加个define定义就行。一开始在头部加
#define _WINSOCK_DEPRECATED_NO_WARNINGS仍然是报错的,后来在项目属性->c++->预处理器里面加上 _WINSOCK_DEPRECATED_NO_WARNINGS就可以运行了。
第三个问题:inet_nota()函数的数据覆盖问题,可参考下面的博客去看,(http://blog.sina.com.cn/s/blog_9f48885501018xm6.html)
inet_ntoa函数的输入参数是u_int型的ip地址,返回的却是指向ip字符串的指针,很明显,ip字符串所占的内存是在函数内部分配的,而我们并不需要释放该内存,所以,它分配的内存是静态的,也就是说下一次调用该函数时会覆盖这个数组的内容。为了解决这个问题,我们只需备份数组里的内容即可。这也就是程序中ARP回环函数中为什么要用memcpy()函数拷贝一下数据内容了。
4)这个是比较简单的问题,但是当时也困惑了好久,后来在同学范鹏的帮助下才解决。
就是在输出结果的 比如结果为0001,但是其输出是1,前面的0就都省去了,这应该是程序语言设计成这样的,其实在前面加一句setw(2) << setfill(‘0’) 就可以了,这个和C语言中的%02x是类似的。这个地方还要在程序头部加一个 #include <iomanip>
setfill,setw,setbase,setprecision这些函数都需要包含这个头文件。
5)正对VS环境的配置,建议参照这个博客,很详细http://www.findspace.name/easycoding/871
最后附上自己编写的抓取三层协议的程序,抓取帧,ip,icmp,ARP的分析程序。
//#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include"stdafx.h"
#include <iostream>
#include <iomanip>
#include"pcap.h"
#include<string>
#include<cstring>
using namespace std;
//ethernet 帧头
struct ethernet
{ //u_char是无符号的char型 而char是有符号的char型,u_char和char都占一个字节,8个bit位
u_int8_t ether_dst[6]; //6位目的地址字段
u_int8_t ether_src[6]; //6位源地址字段
u_short ether_type; //协议类型
};
//ip头部
struct ip
{
#if defined(WORDS_BIENDIAN)
u_int8_t ip_v : 4,
ip_hl : 4;
#else
u_int8_t ip_hl : 4,
ip_v : 4;
#endif
u_int8_t ip_tos; //服务类型 8
u_int16_t ip_len; //总长度 16
u_int16_t ip_id; //标示,标识这个IP数据包.
u_int16_t ip_off; //标志位碎片偏移
u_int8_t ip_ttl; //TTL
u_int8_t ip_pro; //协议类型
u_int16_t ip_cks; //头部校验和
struct in_addr ip_src; //ip源
struct in_addr ip_dst; //目的地址
};
//ICMP header
struct icmp
{
u_int8_t icmp_type; //类型
u_int8_t code; //代码
u_int16_t icmp_cks; //校验和
u_int16_t icmp_id; //标示
u_int16_t icmp_seq; //序列号
};
//ARP header
struct arp
{
u_short arp_hrd; //硬件地址类型
u_short arp_pro; //协议地址类型
u_char arp_hln; //硬件地址长度
u_char arp_pln; //协议地址长度
u_short arp_op; // ARP/RARP 操作
u_char arp_eth_src[6]; //发送站硬件地址MAC
u_char arp_src[4]; //发送站协议地址IP
u_char arp_eth_dst[6]; //目的站硬件地址MAC
u_char arp_dst[4]; //目的站协议地址IP
};
void ip_packet_loop(u_char *pram, const struct pcap_pkthdr* header, const u_char* data);
void arp_packet_loop(u_char *pram, const struct pcap_pkthdr* header, const u_char* data);
void icmp_packet_loop(u_char *pram, const struct pcap_pkthdr* header, const u_char* data);
void display_data(const u_char* data, int length);
void ethernet_packet_loop(u_char *pram, const struct pcap_pkthdr* header, const u_char* data)
{
struct ethernet *eth_h;
eth_h = (struct ethernet*)data;//获得数据包内容
static int p_num = 1; //从第一个数据包开始
cout << endl;
cout << "第" << p_num << "个数据包" << endl;
u_short eth_type;
eth_type = ntohs(eth_h->ether_type);//以太网类型
u_char *macstring1;
u_char *macstring2;
macstring1 = eth_h->ether_src;
macstring2 = eth_h->ether_dst;
cout << "*************帧结构***************" << endl;
cout << " 目标MAC地址: ";
cout << setw(2) << setfill('0') << hex << int(*macstring2) <<":"<< setw(2) << setfill('0') << hex << int(*(macstring2 + 1)) << ":" << setw(2) << setfill('0') << hex << int(*(macstring2 + 2)) << ":" << setw(2) << setfill('0') << hex << int(*(macstring2 + 3)) << ":" << setw(2) << setfill('0') << hex << int(*(macstring2 + 4)) << ":" << setw(2) << setfill('0') << hex << int(*(macstring2 + 5));
cout << endl;
cout << " 源MAC地址: ";
cout <<setw(2)<< setfill('0') << hex << int(*macstring1) << ":" << setw(2)<< setfill('0') << hex << int(*(macstring1+1)) << ":" << setw(2) << setfill('0') << hex << int(*(macstring1+2)) << ":" << setw(2) << setfill('0') << hex << int(*(macstring1 + 3)) << ":" << setw(2) << setfill('0') << hex << int(*(macstring1 + 4)) << ":" << setw(2) << setfill('0') << hex << int(*(macstring1 + 5));
cout << endl;
cout << " 以太网类型为: " << setw(4) << setfill('0') << hex << eth_type << endl;
cout << " 上层协议为: ";
switch (eth_type)
{
case 0x0800: cout << " IP协议 " << endl; break;
case 0x0806: cout << " ARP协议 " << endl; break;
case 0x86dd: cout << " ICMP协议" << endl; break;
default:break;
}
cout << endl;
if (eth_type == 0x0800)//继续分析IP协议
{
ip_packet_loop(pram, header, data);
}
else if (eth_type == 0x0806)
{
arp_packet_loop(pram, header, data);
}
p_num++;
}
void ip_packet_loop(u_char *pram, const struct pcap_pkthdr* header, const u_char* data)
{
struct ip *ip_h;
ip_h = (struct ip *) (data + 14);
u_int offset;
offset = ntohs(ip_h->ip_off);
cout << "*************IP协议*************" << endl;
cout << " 版本号 " << int(ip_h->ip_v) << endl;
cout << " 首部长度 " << int(ip_h->ip_hl) << endl;
cout << " 服务质量 " << ntohs(ip_h->ip_tos) << endl;
cout << " 总长度 " << ntohs(ip_h->ip_len) << endl;
cout << " 标识 " << ntohs(ip_h->ip_id) << endl;
cout << " 偏移 " << setw(4) << setfill('0') << ntohs(ip_h->ip_off)<< endl;
cout << " 生存时间 " << int(ip_h->ip_ttl) << endl;
cout << " 协议类型 " << setw(2) << setfill('0') << int(ip_h->ip_pro) << endl;
cout << " 校验和 " << setw(4) << setfill('0') << ntohs(ip_h->ip_cks) << endl;
cout << " 源IP地址: " << inet_ntoa(ip_h->ip_src) << endl;
cout << " 目的IP地址: " << inet_ntoa(ip_h->ip_dst);
cout << endl;
cout << endl;
if (ip_h->ip_pro == 1) //转到分析Icmp
{
cout << "上层协议是ICMP协议" << endl;
icmp_packet_loop(pram, header, data);
}
}
void arp_packet_loop(u_char *pram, const struct pcap_pkthdr* header, const u_char* data)
{
struct arp * arp_h;
arp_h = (struct arp*)(data + 14);
u_char *macstring1;
u_char *macstring2;
macstring1 = arp_h->arp_eth_src;
macstring2 = arp_h->arp_eth_dst;
switch (ntohs(arp_h->arp_op))
{
case 1:cout << "ARP请求协议" << endl; break;
case 2:cout << "ARP应答协议" << endl; break;
case 3:cout << "RARP请求协议" << endl; break;
case 4:cout << "RARP应答协议" << endl; break;
default:break;
}
cout << "*************ARP协议*************" << endl;
cout << " 硬件类型: " << ntohs(arp_h->arp_hrd) << endl;;
cout << " 协议类型: " << setw(4) << setfill('0') << ntohs(arp_h->arp_pro) << endl;
cout << " 硬件地址长度: " <<int(arp_h->arp_hln) << endl;
cout << " 协议地址长度: " << int(arp_h->arp_pln) << endl;
cout << " 操作码: " << ntohs(arp_h->arp_op) << endl;
cout << " 发送方MAC地址: ";
cout << setw(2) << setfill('0') << hex << int(*macstring1) << ":" << setw(2) << setfill('0') << hex << int(*(macstring1 + 1)) << ":" << setw(2) << setfill('0') << hex << int(*(macstring1 + 2)) << ":" << setw(2) << setfill('0') << hex << int(*(macstring1 + 3)) << ":" << setw(2) << setfill('0') << hex << int(*(macstring1 + 4)) << ":" << setw(2) << setfill('0') << hex << int(*(macstring1 + 5));
cout << endl;
struct in_addr arp_ip_dst;
struct in_addr arp_ip_src;
memcpy((void*)&arp_ip_src, (void*)&arp_h->arp_dst, sizeof(struct in_addr));
cout << " 发送方IP: " << inet_ntoa(arp_ip_src) << endl;
cout << " 接收方MAC地址: ";
cout << setw(2) << setfill('0') << hex << int(*macstring2) << ":" << setw(2) << setfill('0') << hex << int(*(macstring2 + 1)) << ":" << setw(2) << setfill('0') << hex << int(*(macstring2 + 2)) << ":" << setw(2) << setfill('0') << hex << int(*(macstring2 + 3)) << ":" << setw(2) << setfill('0') << hex << int(*(macstring2 + 4)) << ":" << setw(2) << setfill('0') << hex << int(*(macstring2 + 5));
cout << endl;
memcpy((void*)&arp_ip_dst, (void*)&arp_h->arp_dst, sizeof(struct in_addr));
cout << " 接收方IP地址: " << inet_ntoa(arp_ip_dst) << endl;
cout << endl;
}
void icmp_packet_loop(u_char *pram, const struct pcap_pkthdr* header, const u_char* data)
{
struct icmp *icmp_h;
icmp_h = (struct icmp*)(data + 14 + 20);
cout << "*************ICMP协议*************" << endl;
cout << " ICMP类型: " << int(icmp_h->icmp_type)<<endl;//获得ICMP类型
cout << " code: " << ntohs(icmp_h->code) << " ";
switch (icmp_h->icmp_type)
{
cout << " ";
case 0:cout << "回应应答" << endl; break;
case 3:
switch (icmp_h->code)
{
case 0:cout << "网络不可达" << endl; break;
case 1:cout << "主机不可达" << endl; break;
case 2:cout << "协议不可达" << endl; break;
case 3:cout << "端口不可达" << endl; break;
case 4:cout << "需要分段但设置为不允许分段" << endl; break;
case 5:cout << "源路由失败" << endl; break;
case 6:cout << "目的站点网络未知" << endl; break;
case 7:cout << "目的主机网络未知" << endl; break;
case 8:cout << "原主机被隔离" << endl; break;
case 9:cout << "与目的站点网络的通信被禁止" << endl; break;
case 10:cout << "与目的站点主机的通信被禁止" << endl; break;
case 11:cout << "对请求的服务类型,网络不可达" << endl; break;
case 12:cout << "对请求的服务类型,主机不可达" << endl; break;
default:cout << "其他问题" << endl; break;
}
break;
case 4:cout << "源抑制" << endl; break;
case 5:switch (icmp_h->code)
{
case 0:cout << "网络重定向" << endl; break;
case 1:cout << "主机重定向" << endl; break;
case 2:cout << "服务类型和网络重定向" << endl; break;
case 3:cout << "服务类型和主机重定向" << endl; break;
}
case 8:cout << "回应请求" << endl; break;
case 9:cout << "路由器公告" << endl; break;
case 10:cout << "路由器请求" << endl; break;
case 11:switch (icmp_h->code)
{
case 0:cout << "传输期间TTL超时" << endl;
case 1:cout << "数据段组装期间TTL超时" << endl;
}break;
case 12:switch (icmp_h->code)
{
case 0:cout << "损坏的IP头部" << endl;
case 1:cout << "缺少选型" << endl;
}break;
case 13:cout << "时标请求" << endl; break;
case 14:cout << "时标应答" << endl; break;
case 15:cout << "信息请求" << endl; break;
case 16:cout << "信息应答" << endl; break;
case 17:cout << "地址掩码请求" << endl; break;
case 18:cout << "地址掩码应答" << endl; break;
default:break;
}
cout << " ICMP校验和 " << setw(4) << setfill('0')<<ntohs(icmp_h->icmp_cks) << endl;//获得ICMP校验和
cout << " ID " << setw(4) << setfill('0') << ntohs(icmp_h->icmp_id)<<endl;
cout << " 序列号 " << setw(4) << setfill('0') << ntohs(icmp_h->icmp_seq);
cout << endl;
}
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[] = "";
struct bpf_program fcode;
/*
int pcap_findalldebs_ex(
char *source,
struct pcap_rmtauth *auth,
pcap_if_t **alldevs,
char *errbuf);
PCAP_SRC_IF_STRING代表用户想从一个本地文件开始捕获内容
*/
//获得设备列表
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
{
//结果为-1表示获取适配器表失败
cout << "获取设备列表失败";
exit(1);//exit(0)代表正常退出
}
//打印列表
for (d = alldevs; d; d = d->next) //从第一各设备开始,切换到另一个设备
{
cout << ++i << d->name;
if (d->description)
cout << d->description << endl;
else
cout << "没有获得详细信息描述" << endl;
}
if (i == 0)
{
cout << "没有找到接口,请确保您安装了Winpcap程序" << endl;
return -1;
}
cout << "请您输入设备号: ";
cin >> inum;
if (inum<1 || inum>i)
{
cout << "您输入的设备号超出了显示的范围" << endl;
pcap_freealldevs(alldevs);//释放设备列表
return -1;
}
//找到已选设备
for (d = alldevs, i = 0; i < inum - 1; d = d->next, i++);
//打开适配器
if ((adhandle = pcap_open(
d->name, //设备名
65536,
PCAP_OPENFLAG_PROMISCUOUS, //混杂模式
1000, //读取超时
NULL,
errbuf //存储错误信息
)) == NULL)
{
cout << "不能打开适配器,设备不被winpcap支持" << endl;
pcap_freealldevs(alldevs);//释放设备列表
return -1;
}
// 检查数据链路层,k看是否是以太网
if (pcap_datalink(adhandle) != DLT_EN10MB)
{
cout << "这个不工作在以太网范围" << endl;
pcap_freealldevs(alldevs); // 释放设备列表
return -1;
}
if (d->addresses != NULL) //获得接口第一个地址的掩码
netmask = ((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
else
netmask = 0xffffff; //如果接口没有地址,那么我们假设一个C类的掩码
cout << "请输入要过滤的包: " ;
cin >> packet_filter;
// 编译过滤规则
if (pcap_compile(adhandle, //适配器处理对象
&fcode,
packet_filter, //过滤
1, //优化标志
netmask //子网掩码
) < 0)
{
cout << "不能编译这个包" << endl;
pcap_freealldevs(alldevs);
return -1;
}
//设置过滤规则
if (pcap_setfilter(adhandle, &fcode) < 0)
{
cout << "设置过滤规则错误" << endl;
pcap_freealldevs(alldevs);
return -1;
}
int packet_num;//设置抓的包的个数
cout << "请输入抓包个数: " ;
cin >> packet_num;
pcap_loop(adhandle, packet_num, ethernet_packet_loop, NULL); //回调函数
system("pause");
return 0;
}
本文总结了Winpcap编程中遇到的问题,包括类型定义如u_int与int的区别,ntohs()函数在网络字节序转换中的作用,Inet_ntoa()的使用注意事项,以及输出格式调整和VS环境配置的建议。通过这些问题的探讨,有助于理解Winpcap编程的细节和解决常见问题。
4985





