WinPcap笔记(8):分析数据包(2)

来源:互联网 发布:无烟艾条 知乎 编辑:程序博客网 时间:2024/06/11 17:29

上一讲里分析了UDP数据包,这里简单分析一下TCP数据包。

TCP是面向有连接的传输协议,因此相对来说比较复杂。下面是TCP报头的格式:

同样,需要我们自己定义TCP报头:

/* tcp 首部 */typedef struct tcp_header {u_short sport;         //源端口u_short dport;         //目的端口u_int th_seq;             //序列号u_int th_ack;             //确认号u_short doff : 4, hlen : 4, fin : 1, syn : 1, rst : 1, psh : 1, ack : 1, urg : 1, ece : 1, cwr : 1; //4 bits 首部长度,6 bits 保留位,6 bits 标志位u_short th_window;        //窗口大小u_short th_sum;           //校验和u_short th_urp;           //紧急指针}tcp_header;

下面的程序捕获TCP数据包,并将数据包中的数据打印出来:
#include "pcap.h"/* 4字节的IP地址 */typedef struct ip_address {u_char byte1;u_char byte2;u_char byte3;u_char byte4;}ip_address;/* IPv4 首部 */typedef struct ip_header {u_char  ver_ihl;        // 版本 (4 bits) + 首部长度 (4 bits)u_char  tos;            // 服务类型(Type of service) u_short tlen;           // 总长(Total length) u_short identification; // 标识(Identification)u_short flags_fo;       // 标志位(Flags) (3 bits) + 段偏移量(Fragment offset) (13 bits)u_char  ttl;            // 存活时间(Time to live)u_char  proto;          // 协议(Protocol)u_short crc;            // 首部校验和(Header checksum)ip_address  saddr;      // 源地址(Source address)ip_address  daddr;      // 目的地址(Destination address)u_int   op_pad;         // 选项与填充(Option + Padding)}ip_header;/* tcp 首部 */typedef struct tcp_header {u_short sport;         //源端口u_short dport;         //目的端口u_int th_seq;             //序列号u_int th_ack;             //确认号u_short doff : 4, hlen : 4, fin : 1, syn : 1, rst : 1, psh : 1, ack : 1, urg : 1, ece : 1, cwr : 1; //4 bits 首部长度,6 bits 保留位,6 bits 标志位u_short th_window;        //窗口大小u_short th_sum;           //校验和u_short th_urp;           //紧急指针}tcp_header;/* UDP 首部*/typedef struct udp_header {u_short sport;          // 源端口(Source port)u_short dport;          // 目的端口(Destination port)u_short len;            // UDP数据包长度(Datagram length)u_short crc;            // 校验和(Checksum)}udp_header;/* 回调函数原型 */void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);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[] = "ip and tcp";struct bpf_program fcode;/* 获得设备列表 */if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1){fprintf(stderr, "Error in pcap_findalldevs: %s\n", errbuf);exit(1);}/* 打印列表 */for (d = alldevs; d; d = d->next){printf("%d. %s", ++i, d->name);if (d->description)printf(" (%s)\n", d->description);elseprintf(" (No description available)\n");}if (i == 0){printf("\nNo interfaces found! Make sure WinPcap is installed.\n");return -1;}printf("Enter the interface number (1-%d):", i);scanf("%d", &inum);if (inum < 1 || inum > i){printf("\nInterface number out of range.\n");/* 释放设备列表 */pcap_freealldevs(alldevs);return -1;}/* 跳转到已选设备 */for (d = alldevs, i = 0; i< inum - 1;d = d->next, i++);/* 打开适配器 */if ((adhandle = pcap_open(d->name,  // 设备名65536,     // 要捕捉的数据包的部分    // 65535保证能捕获到不同数据链路层上的每个数据包的全部内容PCAP_OPENFLAG_PROMISCUOUS,         // 混杂模式1000,      // 读取超时时间NULL,      // 远程机器验证errbuf     // 错误缓冲池)) == NULL){fprintf(stderr, "\nUnable to open the adapter. %s is not supported by WinPcap\n");/* 释放设备列表 */pcap_freealldevs(alldevs);return -1;}/* 检查数据链路层,为了简单,我们只考虑以太网 */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;//编译过滤器if (pcap_compile(adhandle, &fcode, packet_filter, 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;}printf("\nlistening on %s...\n", d->description);/* 释放设备列表 */pcap_freealldevs(alldevs);/* 开始捕捉 */pcap_loop(adhandle, 0, packet_handler, NULL);return 0;}/* 回调函数,当收到每一个数据包时会被libpcap所调用 */void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data){struct tm *ltime;char timestr[16];ip_header *ih;tcp_header *uh;u_int ip_len;u_int tcp_len;u_short sport, dport;time_t local_tv_sec;/* 将时间戳转换成可识别的格式 */local_tv_sec = header->ts.tv_sec;ltime = localtime(&local_tv_sec);strftime(timestr, sizeof timestr, "%H:%M:%S", ltime);/* 打印数据包的时间戳和长度 */printf("%s.%.6d len:%d ", timestr, header->ts.tv_usec, header->len);/* 获得IP数据包头部的位置 */ih = (ip_header *)(pkt_data +14); //以太网头部长度 /* 获得TCP首部的位置 */ip_len = (ih->ver_ihl & 0xf) * 4;uh = (tcp_header *)((u_char*)ih + ip_len);tcp_len = uh->hlen*4;char *data = (char*)uh + tcp_len;u_int data_len = ntohs(ih->tlen) - ip_len - tcp_len;char buffer[20000];memcpy(buffer, data, data_len);buffer[data_len] = '\0';/* 将网络字节序列转换成主机字节序列 */sport = ntohs(uh->sport);dport = ntohs(uh->dport);/* 打印IP地址和TCP端口 */printf("%d.%d.%d.%d.%d -> %d.%d.%d.%d.%d\n",ih->saddr.byte1,ih->saddr.byte2,ih->saddr.byte3,ih->saddr.byte4,sport,ih->daddr.byte1,ih->daddr.byte2,ih->daddr.byte3,ih->daddr.byte4,dport);printf("ip:%d tcp:%d (%d) data:%s\n",ip_len,tcp_len,data_len, buffer);}

结果如下:


0 0