winpcap发送数据包模拟TCP连接和断开的7次握手
上次我用winpcap成功发送一个UDP数据包到服务器。 相比较TCP要复杂的多, tcp不但要完成开始连接的三次握手, 还要处理数据包的序列号。 下面是tcp七次握手示意图:
下面我就来模拟一个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了。
转:http://blog.csdn.net/liqinghua1653/archive/2009/04/11/4064464.aspx
浙公网安备 33010602011771号