我的腾讯云主机预装了 Windows Server ,截图如下:
在上一篇我给大家介绍了《腾讯云上Winpcap网络编程三之ARP协议获得MAC地址表》。
接下来我们让用户输入要发送的 IP 地址和要发送的数据
u_int ip1,ip2,ip3,ip4;
scanf_s("%d.%d.%d.%d",&ip1,&ip2,&ip3,&ip4);
printf("请输入你要发送的内容:\n");
getchar();
gets_s(TcpData);
printf("要发送的内容:%s\n",TcpData);
声明一下 TcpData
char TcpData[20]; //发送内容
接下来就是重头戏了,需要声明各种结构体,我们发送的是 TCP 数据,这样, TCP 的 TcpData 就作为真正的内容,然后在前面加上 TCP 头,IP 头,帧头,还有校验和要正确。
最后构成一个完整的帧,那么另外声明的结构体如下,前面代码声明过的帧头部结构体就去掉了。
//IP地址格式
struct IpAddress
{
u_char byte1;
u_char byte2;
u_char byte3;
u_char byte4;
};
//帧头部结构体,共14字节
struct EthernetHeader
{
u_char DestMAC[6]; //目的MAC地址 6字节
u_char SourMAC[6]; //源MAC地址 6字节
u_short EthType; //上一层协议类型,如0x0800代表上一层是IP协议,0x0806为arp 2字节
};
//IP头部结构体,共20字节
struct IpHeader
{
unsigned char Version_HLen; //版本信息4位 ,头长度4位 1字节
unsigned char TOS; //服务类型 1字节
short Length; //数据包长度 2字节
short Ident; //数据包标识 2字节
short Flags_Offset; //标志3位,片偏移13位 2字节
unsigned char TTL; //存活时间 1字节
unsigned char Protocol; //协议类型 1字节
short Checksum; //首部校验和 2字节
IpAddress SourceAddr; //源IP地址 4字节
IpAddress DestinationAddr; //目的IP地址 4字节
};
//TCP头部结构体,共20字节
struct TcpHeader
{
unsigned short SrcPort; //源端口号 2字节
unsigned short DstPort; //目的端口号 2字节
unsigned int SequenceNum; //序号 4字节
unsigned int Acknowledgment; //确认号 4字节
unsigned char HdrLen; //首部长度4位,保留位6位 共10位
unsigned char Flags; //标志位6位
unsigned short AdvertisedWindow; //窗口大小16位 2字节
unsigned short Checksum; //校验和16位 2字节
unsigned short UrgPtr; //紧急指针16位 2字节
};
//TCP伪首部结构体 12字节
struct PsdTcpHeader
{
IpAddress SourceAddr; //源IP地址 4字节
IpAddress DestinationAddr; //目的IP地址 4字节
char Zero; //填充位 1字节
char Protcol; //协议号 1字节
unsigned short TcpLen; //TCP包长度 2字节
};
继续 main 函数中对各种结构体的数据进行初始化赋值,并计算校验和。
//结构体初始化为0序列
memset(ðernet, 0, sizeof(ethernet));
BYTE destmac[8];
//目的 MAC 地址,此处没有对帧的 MAC 地址进行赋值,因为网卡设置的混杂模式,可以接受经过该网卡的所有帧。当然最好的方法是赋值为ARP刚才获取到的MAC地址,当然不赋值也可以捕捉到并解析,在此处仅做下说明。
destmac[0] = 0x00;
destmac[1] = 0x11;
destmac[2] = 0x22;
destmac[3] = 0x33;
destmac[4] = 0x44;
destmac[5] = 0x55;
//赋值目的MAC地址
memcpy(ethernet.DestMAC, destmac, 6);
BYTE hostmac[8];
//源MAC地址
hostmac[0] = 0x00;
hostmac[1] = 0x1a;
hostmac[2] = 0x4d;
hostmac[3] = 0x70;
hostmac[4] = 0xa3;
hostmac[5] = 0x89;
//赋值源MAC地址
memcpy(ethernet.SourMAC, hostmac, 6);
//上层协议类型,0x0800代表IP协议
ethernet.EthType = htons(0x0800);
//赋值SendBuffer
memcpy(&SendBuffer, ?ernet, sizeof(struct EthernetHeader));
//赋值IP头部信息
ip.Version_HLen = 0x45;
ip.TOS = 0;
ip.Length = htons(sizeof(struct IpHeader) + sizeof(struct TcpHeader) + strlen(TcpData));
ip.Ident = htons(1);
ip.Flags_Offset = 0;
ip.TTL = 128;
ip.Protocol = 6;
ip.Checksum = 0;
//源IP地址
ip.SourceAddr.byte1 = 127;
ip.SourceAddr.byte2 = 0;
ip.SourceAddr.byte3 = 0;
ip.SourceAddr.byte4 = 1;
//目的IP地址
ip.DestinationAddr.byte1 = ip1;
ip.DestinationAddr.byte2 = ip2;
ip.DestinationAddr.byte3 = ip3;
ip.DestinationAddr.byte4 = ip4;
//赋值SendBuffer
memcpy(&SendBuffer[sizeof(struct EthernetHeader)], &ip, 20);
//赋值TCP头部内容
tcp.DstPort = htons(102);
tcp.SrcPort = htons(1000);
tcp.SequenceNum = htonl(11);
tcp.Acknowledgment = 0;
tcp.HdrLen = 0x50;
tcp.Flags = 0x18;
tcp.AdvertisedWindow = htons(512);
tcp.UrgPtr = 0;
tcp.Checksum = 0;
//赋值SendBuffer
memcpy(&SendBuffer[sizeof(struct EthernetHeader) + 20], &tcp, 20);
//赋值伪首部
ptcp.SourceAddr = ip.SourceAddr;
ptcp.DestinationAddr = ip.DestinationAddr;
ptcp.Zero = 0;
ptcp.Protcol = 6;
ptcp.TcpLen = htons(sizeof(struct TcpHeader) + strlen(TcpData));
//声明临时存储变量,用来计算校验和
char TempBuffer[65535];
memcpy(TempBuffer, &ptcp, sizeof(struct PsdTcpHeader));
memcpy(TempBuffer + sizeof(struct PsdTcpHeader), &tcp, sizeof(struct TcpHeader));
memcpy(TempBuffer + sizeof(struct PsdTcpHeader) + sizeof(struct TcpHeader), TcpData, strlen(TcpData));
//计算TCP的校验和
tcp.Checksum = checksum((USHORT*)(TempBuffer), sizeof(struct PsdTcpHeader) + sizeof(struct TcpHeader) + strlen(TcpData));
//重新把SendBuffer赋值,因为此时校验和已经改变,赋值新的
memcpy(SendBuffer + sizeof(struct EthernetHeader) + sizeof(struct IpHeader), &tcp, sizeof(struct TcpHeader));
memcpy(SendBuffer + sizeof(struct EthernetHeader) + sizeof(struct IpHeader) + sizeof(struct TcpHeader), TcpData, strlen(TcpData));
//初始化TempBuffer为0序列,存储变量来计算IP校验和
memset(TempBuffer, 0, sizeof(TempBuffer));
memcpy(TempBuffer, &ip, sizeof(struct IpHeader));
//计算IP校验和
ip.Checksum = checksum((USHORT*)(TempBuffer), sizeof(struct IpHeader));
//重新把SendBuffer赋值,IP校验和已经改变
memcpy(SendBuffer + sizeof(struct EthernetHeader), &ip, sizeof(struct IpHeader));
//发送序列的长度
int size = sizeof(struct EthernetHeader) + sizeof(struct IpHeader) + sizeof(struct TcpHeader) + strlen(TcpData);
int result = pcap_sendpacket(adhandle, SendBuffer,size);
if (result != 0)
{
printf("Send Error!\n");
}
else
{
printf("Send TCP Packet.\n");
printf("Dstination Port:%d\n", ntohs(tcp.DstPort));
printf("Source Port:%d\n", ntohs(tcp.SrcPort));
printf("Sequence:%d\n", ntohl(tcp.SequenceNum));
printf("Acknowledgment:%d\n", ntohl(tcp.Acknowledgment));
printf("Header Length:%d*4\n", tcp.HdrLen >> 4);
printf("Flags:0x%0x\n", tcp.Flags);
printf("AdvertiseWindow:%d\n", ntohs(tcp.AdvertisedWindow));
printf("UrgPtr:%d\n", ntohs(tcp.UrgPtr));
printf("Checksum:%u\n", ntohs(tcp.Checksum));
printf("Send Successfully!\n");
}
校验和方法如下:
//获得校验和的方法
unsigned short checksum(unsigned short *data, int length)
{
unsigned long temp = 0;
while (length > 1)
{
temp += *data++;
length -= sizeof(unsigned short);
}
if (length)
{
temp += *(unsigned short*)data;
}
temp = (temp >> 16) + (temp &0xffff);
temp += (temp >> 16);
return (unsigned short)(~temp);
}
记得在声明一下这个方法。如果放在 main 函数前当然就不用声明啦。
另外需要声明的变量有
struct EthernetHeader ethernet; //以太网帧头
struct IpHeader ip; //IP头
struct TcpHeader tcp; //TCP头
struct PsdTcpHeader ptcp; //TCP伪首部
unsigned char SendBuffer[200]; ? ? ? //发送队列
接下来的运行结果:
获取MAC地址完毕,请输
121.250.216.112
请输入你要发送的内容
what is tcp
要发送的内容:what i
Send TCP Packet.
Dstination Port:102
Source Port:1000
Sequence:11
Acknowledgment:0
Header Length:5*4
Flags:0x18
AdvertiseWindow:512
UrgPtr:0
Checksum:17149
Send Successfully!
截图如下:
好啦,发送帧到此就告一段落啦!如果有疑问请留言。
帧的接收很简单,直接贴源码如下:
#include <stdio.h>
#include <stdlib.h>
#include <pcap.h>
char *iptos(u_long in); //u_long即为 unsigned long
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);
//struct tm *ltime; //和时间处理有关的变量
struct IpAddress
{
u_char byte1;
u_char byte2;
u_char byte3;
u_char byte4;
};
//帧头部结构体,共14字节
struct EthernetHeader
{
u_char DestMAC[6]; //目的MAC地址 6字节
u_char SourMAC[6]; //源MAC地址 6字节
u_short EthType; //上一层协议类型,如0x0800代表上一层是IP协议,0x0806为arp 2字节
};
//IP头部结构体,共20字节
struct IpHeader
{
unsigned char Version_HLen; //版本信息4位 ,头长度4位 1字节
unsigned char TOS; //服务类型 1字节
short Length; //数据包长度 2字节
short Ident; //数据包标识 2字节
short Flags_Offset; //标志3位,片偏移13位 2字节
unsigned char TTL; //存活时间 1字节
unsigned char Protocol; //协议类型 1字节
short Checksum; //首部校验和 2字节
IpAddress SourceAddr; //源IP地址 4字节
IpAddress DestinationAddr; //目的IP地址 4字节
};
//TCP头部结构体,共20字节
struct TcpHeader
{
unsigned short SrcPort; //源端口号 2字节
unsigned short DstPort; //目的端口号 2字节
unsigned int SequenceNum; //序号 4字节
unsigned int Acknowledgment; //确认号 4字节
unsigned char HdrLen; //首部长度4位,保留位6位 共10位
unsigned char Flags; //标志位6位
unsigned short AdvertisedWindow; //窗口大小16位 2字节
unsigned short Checksum; //校验和16位 2字节
unsigned short UrgPtr; //紧急指针16位 2字节
};
//TCP伪首部结构体 12字节
struct PsdTcpHeader
{
unsigned long SourceAddr; //源IP地址 4字节
unsigned long DestinationAddr; //目的IP地址 4字节
char Zero; //填充位 1字节
char Protcol; //协议号 1字节
unsigned short TcpLen; //TCP包长度 2字节
};
int main(){
EthernetHeader *ethernet; //以太网帧头
IpHeader *ip; //IP头
TcpHeader *tcp; //TCP头
PsdTcpHeader *ptcp; //TCP伪首部
pcap_if_t * alldevs; //所有网络适配器
pcap_if_t *d; //选中的网络适配器
char errbuf[PCAP_ERRBUF_SIZE]; //错误缓冲区,大小为256
char source[PCAP_ERRBUF_SIZE];
pcap_t *adhandle; //捕捉实例,是pcap_open返回的对象
int i = 0; //适配器计数变量
struct pcap_pkthdr *header; //接收到的数据包的头部
const u_char *pkt_data; //接收到的数据包的内容
int res; //表示是否接收到了数据包
u_int netmask; //过滤时用的子网掩码
char packet_filter[] = "tcp"; //过滤字符
struct bpf_program fcode; //pcap_compile所调用的结构体
u_int ip_len; //ip地址有效长度
u_short sport,dport; //主机字节序列
u_char packet[100]; //发送数据包目的地址
pcap_dumper_t *dumpfile; //堆文件
//time_t local_tv_sec; //和时间处理有关的变量
//char timestr[16]; //和时间处理有关的变量
//获取本地适配器列表
if(pcap_findalldevs_ex(PCAP_SRC_IF_STRING,NULL,&alldevs,errbuf) == -1){
//结果为-1代表出现获取适配器列表失败
fprintf(stderr,"Error in pcap_findalldevs_ex:\n",errbuf);
//exit(0)代表正常退出,exit(other)为非正常退出,这个值会传给操作系统
exit(1);
}
//打印设备列表信息
for(d = alldevs;d !=NULL;d = d->next){
printf("-----------------------------------------------------------------\nnumber:%d\nname:%s\n",++i,d->name);
if(d->description){
//打印适配器的描述信息
printf("description:%s\n",d->description);
}else{
//适配器不存在描述信息
printf("description:%s","no description\n");
}
//打印本地环回地址
printf("\tLoopback: %s\n",(d->flags & PCAP_IF_LOOPBACK)?"yes":"no");
pcap_addr_t *a; //网络适配器的地址用来存储变量
for(a = d->addresses;a;a = a->next){
//sa_family代表了地址的类型,是IPV4地址类型还是IPV6地址类型
switch (a->addr->sa_family)
{
case AF_INET: //代表IPV4类型地址
printf("Address Family Name:AF_INET\n");
if(a->addr){
//->的优先级等同于括号,高于强制类型转换,因为addr为sockaddr类型,对其进行操作须转换为sockaddr_in类型
printf("Address:%s\n",iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr));
}
if (a->netmask){
printf("\tNetmask: %s\n",iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr));
}
if (a->broadaddr){
printf("\tBroadcast Address: %s\n",iptos(((struct sockaddr_in *)a->broadaddr)->sin_addr.s_addr));
}
if (a->dstaddr){
printf("\tDestination Address: %s\n",iptos(((struct sockaddr_in *)a->dstaddr)->sin_addr.s_addr));
}
break;
case AF_INET6: //代表IPV6类型地址
printf("Address Family Name:AF_INET6\n");
printf("this is an IPV6 address\n");
break;
default:
break;
}
}
}
//i为0代表上述循环未进入,即没有找到适配器,可能的原因为Winpcap没有安装导致未扫描到
if(i == 0){
printf("interface not found,please check winpcap installation");
}
int num;
printf("Enter the interface number(1-%d):",i);
//让用户选择选择哪个适配器进行抓包
scanf_s("%d",&num);
printf("\n");
//用户输入的数字超出合理范围
if(num<1||num>i){
printf("number out of range\n");
pcap_freealldevs(alldevs);
return -1;
}
//跳转到选中的适配器
for(d=alldevs, i=0; i< num-1 ; d=d->next, i++);
//运行到此处说明用户的输入是合法的
if((adhandle = pcap_open(d->name, //设备名称
65535, //存放数据包的内容长度
PCAP_OPENFLAG_PROMISCUOUS, //混杂模式
1000, //超时时间
NULL, //远程验证
errbuf //错误缓冲
)) == NULL){
//打开适配器失败,打印错误并释放适配器列表
fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n", d->name);
// 释放设备列表
pcap_freealldevs(alldevs);
return -1;
}
//打印输出,正在监听中
printf("\nlistening on %s...\n", d->description);
//所在网络不是以太网,此处只取这种情况
if(pcap_datalink(adhandle) != DLT_EN10MB)
{
fprintf(stderr,"\nThis program works only on Ethernet networks.\n");
//释放列表
pcap_freealldevs(alldevs);
return -1;
}
//先获得地址的子网掩码
if(d->addresses != NULL)
//获得接口第一个地址的掩码
netmask=((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
else
// 如果接口没有地址,那么我们假设一个C类的掩码
netmask=0xffffff;
//pcap_compile()的原理是将高层的布尔过滤表
//达式编译成能够被过滤引擎所解释的低层的字节码
if(pcap_compile(adhandle, //适配器处理对象
&fcode,
packet_filter, //过滤ip和UDP
1, //优化标志
netmask //子网掩码
)<0)
{
//过滤出现问题
fprintf(stderr,"\nUnable to compile the packet filter. Check the syntax.\n");
// 释放设备列表
pcap_freealldevs(alldevs);
return -1;
}
//设置过滤器
if (pcap_setfilter(adhandle, &fcode)<0)
{
fprintf(stderr,"\nError setting the filter.\n");
//释放设备列表
pcap_freealldevs(alldevs);
return -1;
}
//利用pcap_next_ex来接受数据包
while((res = pcap_next_ex(adhandle,&header,&pkt_data))>=0)
{
if(res ==0){
//返回值为0代表接受数据包超时,重新循环继续接收
continue;
}else{
//运行到此处代表接受到正常从数据包
//header为帧的头部
printf("%.6ld len:%d ", header->ts.tv_usec, header->len);
// 获得IP数据包头部的位置
ip = (IpHeader *) (pkt_data +14); //14为以太网帧头部长度
//获得TCP头部的位置
ip_len = (ip->Version_HLen & 0xf) *4;
printf("ip_length:%d ",ip_len);
tcp = (TcpHeader *)((u_char *)ip+ip_len);
char * data;
data = (char *)((u_char *)tcp+20);
//将网络字节序列转换成主机字节序列
sport = ntohs( tcp->SrcPort );
dport = ntohs( tcp->DstPort );
printf("srcport:%d desport:%d\n",sport,dport);
printf("%d.%d.%d.%d.%d -> %d.%d.%d.%d.%d\n",
ip->SourceAddr.byte1,
ip->SourceAddr.byte2,
ip->SourceAddr.byte3,
ip->SourceAddr.byte4,
sport,
ip->DestinationAddr.byte1,
ip->DestinationAddr.byte2,
ip->DestinationAddr.byte3,
ip->DestinationAddr.byte4,
dport);
printf("%s\n",data);
}
}
//释放网络适配器列表
pcap_freealldevs(alldevs);
/**
int pcap_loop ( pcap_t * p,
int cnt,
pcap_handler callback,
u_char * user
);
typedef void (*pcap_handler)(u_char *, const struct pcap_pkthdr *,
const u_char *);
*/
//开始捕获信息,当捕获到数据包时,会自动调用这个函数
//pcap_loop(adhandle,0,packet_handler,NULL);
int inum;
scanf_s("%d", &inum);
return 0;
}
/* 每次捕获到数据包时,libpcap都会自动调用这个回调函数 */
/**
pcap_loop()函数是基于回调的原理来进行数据捕获的,如技术文档所说,这是一种精妙的方法,并且在某些场合下,
它是一种很好的选择。但是在处理回调有时候会并不实用,它会增加程序的复杂度,特别是在多线程的C++程序中
*/
/*
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data)
{
struct tm *ltime = NULL;
char timestr[16];
time_t local_tv_sec;
// 将时间戳转换成可识别的格式
local_tv_sec = header->ts.tv_sec;
localtime_s(ltime,&local_tv_sec);
strftime( timestr, sizeof timestr, "%H:%M:%S", ltime);
printf("%s,%.6ld len:%d\n", timestr, header->ts.tv_usec, header->len);
}
*/
/* 将数字类型的IP地址转换成字符串类型的 */
#define IPTOSBUFFERS 12
char *iptos(u_long in)
{
static char output[IPTOSBUFFERS][3*4+3+1];
static short which;
u_char *p;
p = (u_char *)∈
which = (which + 1 == IPTOSBUFFERS ? 0 : which + 1);
sprintf_s(output[which], "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
return output[which];
}
运行截图如下
Thank You
如有问题,欢迎留言~
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。