您的位置:首页 > 理论基础 > 计算机网络

winpcap发送数据包模拟TCP连接和断开的7次握手

2009-04-11 11:20 435 查看
上次我用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了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: