winpcap发送数据包模拟TCP连接和断开的7次握手
2009-04-11 11:20
435 查看
上次我用winpcap成功发送一个UDP数据包到服务器。 相比较TCP要复杂的多, tcp不但要完成开始连接的三次握手, 还要处理数据包的序列号。 下面是tcp七次握手示意图:
下面我就来模拟一个TCP客服端,发送一个连接到服务器, 服务器之后断开。这样就是7次握手, 不含其他数据包。
首先我们做一个tcp服务器, 最好是一个公网的, 这样连接就更有真实性。
服务器代码:
好了服务器是做好了, 现在主要是做客服端了。在发送数据包之前, 我们要知道数据包的结构, 对一个TCP数据包结构像这个样子:
【以太网头】【IP头】【TCP头】【数据】
由于握手过程数据可以空, 所以只有三个部分。 各种数据包头如下声明(注意我这里给出的是小端模式), 大部分数从linux系统里面copy过来。 对数据包详细信息, 这里就不多说了。
好了, 现在可以发送数据包了, 我们发送四个数据包, 服务器回复三个数据包。
1. client发送SYN数据包
2. 接收server syn和ack包
3. 发送ack包
4. 接收fin数据包
5. 回复ack
6. 发送fin
7. 接收ack
这里我用两个线程来处理, 主线程接收数据包(pcap_loop), 一个子线程负责发送数据包(send_packet_handle)。 在构建数据报的时候一定要注意 TCP的seq 和ack_seq的更新, 下一个发送的seq值等于上一个收到的ack_seq。 下一个seq_ack为上一个接收的seq+1. 这里只是一个简单的模拟程序, 没有考虑两个线程之间的数据同步。
运行结果服务器成功收到:
From IP = 116.227.*.* Port = 10999
说明我们的连接成功了。
注意事项:
1. 很可能在收到服务器的数据包时候操作系统会发送一个RST给服务器, 这样连接就失败。 我还没有很好的办法阻止这个数据包的发送。 在我的机器上如果打开防火墙, 就不会产生RST数据包, 如果关闭就会产生。
2. 如果有些值不知道怎么设置, 可以查看一些资料。 同时也可以用socket可以自己先做一个client, connect到服务器, 获取数据包, 看看这些数据包是怎么设置的。
3. 两个mac地址我全部设置了1, 目前我还不确定这些有什么用, 以后有时间再研究一下。
4. 注意很有可能一个数据包可能发送多次, 主要是服务器在一定时间没有收到答复, 会重新发送。 注意我的程序没有处理这样的情况。
5. 注意字节序的问题, 我开始就是一个地方忘了转换, 导致数据包不能成功收到。
ok了。
下面我就来模拟一个TCP客服端,发送一个连接到服务器, 服务器之后断开。这样就是7次握手, 不含其他数据包。
首先我们做一个tcp服务器, 最好是一个公网的, 这样连接就更有真实性。
服务器代码:
#include <Winsock2.h> #include <process.h> #include <stdio.h> #include <stdlib.h> #pragma comment (lib,"ws2_32.lib") SOCKET tcpServerSocket = 0; SOCKADDR_IN tcpServerAddr = { 0 }; const int backlog = SOMAXCONN; WSADATA wsaData = { 0 }; int ret_code; #define PORT 7456 //server port #define IPSTRING "122.*.*.*" //server ip //³ö´í´¦Àíº¯Êý void error(const char* errstr, int errcode) { printf("Error: %s/n", errstr); printf("Error Code: %s/n", errcode); } void error_exit(const char* errstr, int errcode) { error(errstr, errcode); exit(errcode); } void main() { ret_code = WSAStartup( MAKEWORD( 2, 2 ), &wsaData); if ( ret_code != 0 ) { error_exit("WSAStartup error", ret_code); } //´´½¨Ì×½Ó×Ö if((tcpServerSocket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) error_exit("socket error", WSAGetLastError()); //°ó¶¨IPºÍ¶Ë¿Ú tcpServerAddr.sin_family = AF_INET; tcpServerAddr.sin_port = htons(PORT); tcpServerAddr.sin_addr.S_un.S_addr = inet_addr(IPSTRING); if((bind(tcpServerSocket, (struct sockaddr *)&tcpServerAddr, sizeof(tcpServerAddr))) == SOCKET_ERROR) error_exit("bind error", WSAGetLastError()); if((listen(tcpServerSocket, backlog)) == SOCKET_ERROR) error_exit("listen error", WSAGetLastError()); while(true) { SOCKET tcpClientSocket = 0; SOCKADDR_IN tcpClientAddr = { 0 }; int tcpClientAddrLen = sizeof(SOCKADDR_IN); if((tcpClientSocket = accept(tcpServerSocket, (struct sockaddr *)&tcpClientAddr, &tcpClientAddrLen)) == INVALID_SOCKET) { error_exit("accept error", WSAGetLastError()); continue; } printf("From IP = %s Port = %d/n", inet_ntoa(tcpClientAddr.sin_addr), ntohs(tcpClientAddr.sin_port)); //·¢ËͺͽÓÊÕÊý¾Ý //recv //send closesocket(tcpClientSocket); } }
好了服务器是做好了, 现在主要是做客服端了。在发送数据包之前, 我们要知道数据包的结构, 对一个TCP数据包结构像这个样子:
【以太网头】【IP头】【TCP头】【数据】
由于握手过程数据可以空, 所以只有三个部分。 各种数据包头如下声明(注意我这里给出的是小端模式), 大部分数从linux系统里面copy过来。 对数据包详细信息, 这里就不多说了。
#include <pcap.h> #pragma comment(lib, "wpcap.lib") #include <stdio.h> #pragma comment(lib, "Ws2_32.lib") #define ETHER_ADDR_LEN 6 //from linux's ethernet.h #define ETHERTYPE_IP 0x0800 /* IP */ //以太网数据头 struct ether_header{ u_char ether_dhost[ETHER_ADDR_LEN]; u_char ether_shost[ETHER_ADDR_LEN]; u_short ether_type; //如果上一层为IP协议。则ether_type的值就是0x0800 }; //IP数据头 struct ip_header //小端模式 { unsigned char ihl:4; //ip header length unsigned char version:4; //version u_char tos; //type of service u_short tot_len; //total length u_short id; //identification u_short frag_off; //fragment offset u_char ttl; //time to live u_char protocol; //protocol type u_short check; //check sum u_int saddr; //source address u_int daddr; //destination address }; //tcp数据头 struct tcp_header //小端模式 { u_int16_t source; u_int16_t dest; u_int32_t seq; u_int32_t ack_seq; u_int16_t res1:4; u_int16_t doff:4; u_int16_t fin:1; u_int16_t syn:1; u_int16_t rst:1; u_int16_t psh:1; u_int16_t ack:1; u_int16_t urg:1; u_int16_t res2:2; u_int16_t window; u_int16_t check; u_int16_t urg_ptr; }; //tcp和udp计算校验和是的伪头 struct psd_header { u_int32_t sourceip; //源IP地址 u_int32_t destip; //目的IP地址 u_char mbz; //置空(0) u_char ptcl; //协议类型 u_int16_t plen; //TCP/UDP数据包的长度(即从TCP/UDP报头算起到数据包结束的长度 单位:字节) };
好了, 现在可以发送数据包了, 我们发送四个数据包, 服务器回复三个数据包。
1. client发送SYN数据包
2. 接收server syn和ack包
3. 发送ack包
4. 接收fin数据包
5. 回复ack
6. 发送fin
7. 接收ack
这里我用两个线程来处理, 主线程接收数据包(pcap_loop), 一个子线程负责发送数据包(send_packet_handle)。 在构建数据报的时候一定要注意 TCP的seq 和ack_seq的更新, 下一个发送的seq值等于上一个收到的ack_seq。 下一个seq_ack为上一个接收的seq+1. 这里只是一个简单的模拟程序, 没有考虑两个线程之间的数据同步。
char* device = "//Device//NPF_{06864041-9387-44DC-AF44-37779B0F2E9E}"; pcap_t* adhandle = NULL; char errbuf[PCAP_ERRBUF_SIZE] = { 0 }; unsigned int netmask=0xffffff; struct bpf_program fcode; char* filter = "tcp"; #include <process.h> #define DSTPORT 7456 #define SRCPORT 7456 #define DSTIP "*.*.*.*" #define SRCIP "*.*.*.*" bool isShouldReply = false; //step = 0, 接收 syn ack //step = 1, 接收 fin //step = 2,接收 fin之后ack int step = 0; //分别表示发送段和接收端开始系列号 unsigned int next_seq = 0x42345678; unsigned int next_ack_seq = 0; //用一个线程检测从目的地发过来的数据包. //另一个线程向目的地发送数据包. //负责发送数据包 void send_packet_handle(void* arg); //负责接收数据包, pcap_loop参数用于回调 void my_pcap_handler(u_char *user, const struct pcap_pkthdr *pkt_header, const u_char *pkt_data); unsigned short do_check_sum(void* buffer, int len); char* uint_to_addr(u_int addr); void main() { if((adhandle = pcap_open(device, 0x10000, PCAP_OPENFLAG_PROMISCUOUS, 1000, NULL, errbuf)) == NULL) { printf("[pcap_open error] : %s/n", errbuf); return; } if(pcap_compile(adhandle, &fcode, filter, 1, netmask) == -1) { printf("[pcap_compile error] : %s/n", pcap_geterr(adhandle)); return; } if(pcap_setfilter(adhandle, &fcode) == -1) { printf("[pcap_setfilter error] : %s/n", pcap_geterr(adhandle)); return; } unsigned long p = _beginthread(send_packet_handle, 0, NULL); pcap_loop(adhandle, 0, my_pcap_handler, NULL); } void my_pcap_handler(u_char *user, const struct pcap_pkthdr *pkt_header, const u_char *pkt_data) { ether_header* pe_header = (ether_header*)pkt_data; if(pe_header->ether_type == htons(ETHERTYPE_IP)) { ip_header* p_ip_header = (ip_header*)(pkt_data + sizeof(ether_header)); if(p_ip_header->saddr == p_ip_header->daddr) { printf("%s/n", uint_to_addr(p_ip_header->saddr)); } //IP地址检验 if(p_ip_header->protocol == IPPROTO_TCP || (p_ip_header->saddr == inet_addr(DSTIP) && p_ip_header->daddr == inet_addr(SRCIP)) || (p_ip_header->saddr == inet_addr(SRCIP) && p_ip_header->daddr == inet_addr(DSTIP))) { tcp_header* ptcpHeader = (tcp_header*)(pkt_data + sizeof(ether_header) + p_ip_header->ihl * 4); //端口检验 if(ptcpHeader->dest == htons(DSTPORT) || ptcpHeader->source == htons(DSTPORT) ) { //打印信息 printf("[%s:%d /t-> ", uint_to_addr(p_ip_header->saddr), ntohs(ptcpHeader->source)); printf("%s:%d] len = %u type: ", uint_to_addr(p_ip_header->daddr),ntohs(ptcpHeader->dest), ntohs(p_ip_header->tot_len) - sizeof(ip_header) - sizeof(tcp_header)); if(ptcpHeader->syn == 1) printf("SYN = %u ", ntohl(ptcpHeader->seq)); if(ptcpHeader->ack == 1) printf("ACK = %u ", ntohl(ptcpHeader->ack_seq)); if(ptcpHeader->fin == 1) printf("FIN = %u ", ntohl(ptcpHeader->seq)); if(ptcpHeader->rst == 1) printf("RST "); if(ptcpHeader->psh == 1) printf("PSH "); printf("/n"); if(step == 0 && ptcpHeader->syn == 1 && ptcpHeader->ack == 1 && ptcpHeader->ack_seq == htonl(next_seq + 1)) { next_ack_seq = ntohl(ptcpHeader->seq) + 1; next_seq = ntohl(ptcpHeader->ack_seq); isShouldReply = true; } else if(step == 1 && ptcpHeader->fin == 1) { next_ack_seq = ntohl(ptcpHeader->seq) + 1; next_seq = ntohl(ptcpHeader->ack_seq); isShouldReply = true; } /* else if(step == 2 && ptcpHeader->ack == 1 && ptcpHeader->seq == next_ack_seq) { isShouldReply = true; }*/ } } } } void send_packet_handle(void* arg) { //三次握手不含数据 char tcp_buffer[54] = { 0 }; //14 以太网头部, 20 ip头, 20 tcp头, 没有其它数据 ether_header* pether_header = (ether_header*)tcp_buffer; ip_header* pip_header = (ip_header*)(tcp_buffer + sizeof(ether_header)); tcp_header* ptcp_header = (tcp_header*)(tcp_buffer + sizeof(ether_header) + sizeof(ip_header)); //设置以太网头部 //所有MAC位置1 //unsigned char mac[] = {0,238,238,129,65,238,0,29,96,102,102,15}; //memcpy(pether_header, mac, 12); memset(pether_header, 1, 12); pether_header->ether_type = htons(ETHERTYPE_IP); //设置IP头 pip_header->ihl = sizeof(ip_header) / 4; pip_header->version = 4; pip_header->tos = 0; pip_header->tot_len = htons(sizeof(tcp_buffer) - sizeof(ether_header)); //12288 pip_header->id = 616; //616 pip_header->frag_off = 64; pip_header->ttl = 128; pip_header->protocol = IPPROTO_TCP; pip_header->check = 0; pip_header->saddr = inet_addr(SRCIP); pip_header->daddr = inet_addr(DSTIP); pip_header->check = in_cksum((u_int16_t*)pip_header, sizeof(ip_header)); //设置TCP头 ptcp_header->source = htons(SRCPORT); ptcp_header->dest = htons(DSTPORT); ptcp_header->seq = htonl(next_seq); ptcp_header->ack_seq = htonl(next_ack_seq); ptcp_header->doff = sizeof(tcp_header) / 4; ptcp_header->res1 = 0; ptcp_header->res2 = 0; ptcp_header->fin = 0; ptcp_header->syn = 1; ptcp_header->rst = 0; ptcp_header->psh = 0; ptcp_header->ack = 0; ptcp_header->urg = 0; ptcp_header->window = 65535; ptcp_header->check = 0; ptcp_header->urg_ptr = 0; ptcp_header->check = do_check_sum(ptcp_header, sizeof(tcp_header)); //发送一个SYN包到对方 if(pcap_sendpacket(adhandle, (const u_char*)tcp_buffer, sizeof(tcp_buffer)) == -1) { printf("[pcap_sendpacket error]/n"); return; } while(true) { if(isShouldReply) break; Sleep(100); } //发送一个ACK包到对方 ptcp_header->syn = 0; ptcp_header->ack = 1; ptcp_header->seq = htonl(next_seq); ptcp_header->ack_seq = htonl(next_ack_seq); ptcp_header->check = 0; ptcp_header->check = do_check_sum(ptcp_header, sizeof(tcp_header)); isShouldReply = false; step = 1; if(pcap_sendpacket(adhandle, (const u_char*)tcp_buffer, sizeof(tcp_buffer)) == -1) { printf("[pcap_sendpacket error]/n"); return; } while(true) { if(isShouldReply) break; Sleep(100); } //发送fin回复的ack包 ptcp_header->ack = 1; ptcp_header->seq = htonl(next_seq); ptcp_header->ack_seq = htonl(next_ack_seq); ptcp_header->check = 0; ptcp_header->check = do_check_sum(ptcp_header, sizeof(tcp_header)); isShouldReply = false; if(pcap_sendpacket(adhandle, (const u_char*)tcp_buffer, sizeof(tcp_buffer)) == -1) { printf("[pcap_sendpacket error]/n"); return; } //接着发送一个fin, ack包 ptcp_header->ack = 1; ptcp_header->fin = 1; ptcp_header->seq = htonl(next_seq); ptcp_header->ack_seq = htonl(next_ack_seq); ptcp_header->check = 0; ptcp_header->check = do_check_sum(ptcp_header, sizeof(tcp_header)); isShouldReply = false; step = 2; if(pcap_sendpacket(adhandle, (const u_char*)tcp_buffer, sizeof(tcp_buffer)) == -1) { printf("[pcap_sendpacket error]/n"); return; } printf("Finished/n"); } char* uint_to_addr(u_int addr) { in_addr inaddr; inaddr.S_un.S_addr = addr; return inet_ntoa(inaddr); } unsigned short do_check_sum(void* buffer, int len) { char buffer2[128] = { 0 }; psd_header* psd = (psd_header*)buffer2; psd->sourceip = inet_addr(SRCIP); psd->destip = inet_addr(DSTIP); psd->ptcl = IPPROTO_TCP; psd->plen = htons(sizeof(tcp_header)); memcpy(buffer2 + sizeof(psd_header), buffer, sizeof(tcp_header)); return in_cksum((u_int16_t*)buffer2, sizeof(psd_header) + sizeof(tcp_header)); }
运行结果服务器成功收到:
From IP = 116.227.*.* Port = 10999
说明我们的连接成功了。
注意事项:
1. 很可能在收到服务器的数据包时候操作系统会发送一个RST给服务器, 这样连接就失败。 我还没有很好的办法阻止这个数据包的发送。 在我的机器上如果打开防火墙, 就不会产生RST数据包, 如果关闭就会产生。
2. 如果有些值不知道怎么设置, 可以查看一些资料。 同时也可以用socket可以自己先做一个client, connect到服务器, 获取数据包, 看看这些数据包是怎么设置的。
3. 两个mac地址我全部设置了1, 目前我还不确定这些有什么用, 以后有时间再研究一下。
4. 注意很有可能一个数据包可能发送多次, 主要是服务器在一定时间没有收到答复, 会重新发送。 注意我的程序没有处理这样的情况。
5. 注意字节序的问题, 我开始就是一个地方忘了转换, 导致数据包不能成功收到。
ok了。
相关文章推荐
- tcp/ip 三次握手(建立连接)和四次挥手(断开连接)
- TCP协议三次握手连接四次握手断开和DOS攻击
- TCP 三次握手四次挥手及理由(二 连接与断开)
- TCP3次握手连接协议和4次握手断开连接协议
- TCP四次握手断开连接(图解)
- 图片详解TCP连接的三次握手,四次断开基本原理
- TCP连接——三次握手和四次断开
- TCP建立连接及断开的几次握手过程
- TCP的三次握手和四次断开 TCP半连接
- TCP建立连接的三次握手与断开连接的四次握手
- 利用tcpdump抓包工具监控TCP连接的三次握手和断开连接的四次挥手
- TCP连接时三次握手协议,断开连接四次挥手协议
- TCP协议三次握手连接四次握手断开和DOS攻击
- java---网络知识点---TCP三次握手连接 断开四次挥手
- 简单说说TCP(3) --- 断开连接四次握手
- TCP四次握手断开连接
- TCP 三次握手连接&四次握手断开
- TCP3次握手连接协议和4次握手断开连接协议
- Qt模拟串口-tcp连接发送数据
- TCP协议的三次握手建立连接及四次握手断开连接