你好,游客 登录
背景:
阅读新闻

腾讯云上Winpcap网络编程四之主机通信

[日期:2017-04-13] 来源:腾讯云  作者: [字体: ]

  学习资源

  我的腾讯云主机预装了Windows Server,截图如下:

  

image

 

  两台主机通信实战

  在上一篇我给大家介绍了《腾讯云上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!

  截图如下:

  

image

 

  好啦,发送帧到此就告一段落啦!如果有疑问请留言。

  帧的接收很简单,直接贴源码如下:

  #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 *)&in;

  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];

  }

  运行截图如下

  

image

 

  

image

  Thank You

推荐 打印 | 录入: | 阅读:
相关新闻      
本文评论   
评论声明
  • 尊重网上道德,遵守中华人民共和国的各项有关法律法规
  • 承担一切因您的行为而直接或间接导致的民事或刑事法律责任
  • 本站管理人员有权保留或删除其管辖留言中的任意内容
  • 本站有权在网站内转载或引用您的评论
  • 参与本评论即表明您已经阅读并接受上述条款