资源描述
实验三 发送TCP数据包
实验目的:
• 设计一个发送TCP数据包的程序,并根据本设计说明TCP数据包的结构以及TCP协议与IP协议的关系,使大家对TCP协议的工作原理有更深入的认识。
•
实验要求:
本程序的功能是填充一个TCP数据包,并发送给目的主机。
• 以命令行形式运行:SendTCP source_ip source_port dest_ip dest_port
其中SendTCP为程序名;source_ip为源IP地址; source_port为源端口; dest_ip为目的IP地址; dest_port为目的端口。
• 其他的TCP头部参数自行设定。
• 数据字段为“This is my homework of network!”.
• 成功发送后在屏幕上输出“send OK”。
课程设计分析:
• 使用原始套接字
• 定义IP头部、TCP头部和伪头部的数据结构
• 填充数据包
• 发送数据包
设计思想:
本课程设计的目标是发送一个TCP数据包,可以利用原始套接字来完成这个工作。整个程序由初始化原始套接字和发送TCP数据包两个部分组成。
创建一个原始套接字,并设置IP头选项
SOCKET sock;
sock = socket(AF_INET,SOCK_RAW,IPPROTO_IP);
或者:
sock=WSASoccket(AF_INET,SOCK_RAW,IPPROTO_IP,NULL,0,WSA_FLAG_OVERLAPPED);
设置SOCK_RAW标志,表示我们声明的是一个原始套接字类型。
为使用发送接收超时设置,必须将标志位置位置为WSA_FLAG_OVERLAPPED。在本课程设计中,发送TCP包时隐藏了自己的IP地址,因此我们要自己填充IP头,设置IP头操作选项。其中flag设置为ture,并设定 IP_HDRINCL 选项,表明自己来构造IP头。注意,如果设置IP_HDRINCL 选项,那么必须具有 administrator权限,要不就必须修改注册表:
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Afd\Parameter\
修改键:DisableRawSecurity(类型为DWORD),把值修改为 1。如果没有,就添加。
BOOL Flag=TRUE;
setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char *)&Flag, sizeof(Flag));
int timeout=1000;
setsockopt(sock, SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout, sizeof(timeout));
在这里我们使用基本套接字SOL_SOCKET,设置SO_SNDTIMEO表示使用发送超时设置,超时时间设置为1000ms。
构造IP头和TCP头
这里, IP头和TCP头以及TCP伪部的构造请参考下面它们的数据结构。
计算校验和的子函数
在填充数据包的过程中,需要调用计算校验和的函数checksum两次,分别用于校验IP头和TCP头部(加上伪头部),其实现代码如下:
USHORT checksum(USHORT *buffer, int size)
{
unsigned long cksum=0;
while(size >1)
{
cksum+=*buffer++;
size -=sizeof(USHORT);
}
if(size )
{
cksum += *(UCHAR*)buffer;
}
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >>16);
return (USHORT)(~cksum);
}
程序流程图:
源程序代码:
#include <stdio.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <time.h>
#include <windows.h>
#include <string.h>
#include <stdlib.h>
#include <iostream.h>
#pragma comment(lib,"ws2_32.lib")
#define IPVER 4 //IP协议预定
#define MAX_BUFF_LEN 65500 //发送缓冲区最大值
typedef struct ip_hdr //定义IP首部
{
UCHAR h_verlen; //4位首部长度,4位IP版本号
UCHAR tos; //8位服务类型TOS
USHORT total_len; //16位总长度(字节)
USHORT ident; //16位标识
USHORT frag_and_flags; //3位标志位
UCHAR ttl; //8位生存时间 TTL
UCHAR proto; //8位协议 (TCP, UDP 或其他)
USHORT checksum; //16位IP首部校验和
ULONG sourceIP; //32位源IP地址
ULONG destIP; //32位目的IP地址
}IP_HEADER;
typedef struct tsd_hdr //定义TCP伪首部
{
ULONG saddr; //源地址
ULONG daddr; //目的地址
UCHAR mbz; //没用
UCHAR ptcl; //协议类型
USHORT tcpl; //TCP长度
}PSD_HEADER;
typedef struct tcp_hdr //定义TCP首部
{
USHORT th_sport; //16位源端口
USHORT th_dport; //16位目的端口
ULONG th_seq; //32位序列号
ULONG th_ack; //32位确认号
UCHAR th_lenres; //4位首部长度/6位保留字
UCHAR th_flag; //6位标志位
USHORT th_win; //16位窗口大小
USHORT th_sum; //16位校验和
USHORT th_urp; //16位紧急数据偏移量
}TCP_HEADER;
//CheckSum:计算校验和的子函数
USHORT checksum(USHORT *buffer, int size)
{
unsigned long cksum=0;
while(size >1)
{
cksum+=*buffer++;
size -=sizeof(USHORT);
}
if(size)
{
cksum += *(UCHAR*)buffer;
}
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >>16);
return (USHORT)(~cksum);
}
int main(int argc, char* argv[])
{
WSADATA WSAData;
SOCKET sock;
IP_HEADER ipHeader;
TCP_HEADER tcpHeader;
PSD_HEADER psdHeader;
char Sendto_Buff[MAX_BUFF_LEN]; //发送缓冲区
unsigned short check_Buff[MAX_BUFF_LEN]; //检验和缓冲区
const char tcp_send_data[]={"This is my homework of networt,I am happy!"};
BOOL flag;
int rect,nTimeOver;
if (argc!= 5)
{
printf("Useage: SendTcp soruce_ip source_port dest_ip dest_port \n");
return false;
}
if (WSAStartup(MAKEWORD(2,2), &WSAData)!=0)
{
printf("WSAStartup Error!\n");
return false;
}
if((sock=WSASocket(AF_INET,SOCK_RAW,IPPROTO_RAW,NULL,0,
WSA_FLAG_OVERLAPPED))==INVALID_SOCKET)
{
printf("Socket Setup Error!\n");
return false;
}
flag=true;
if(setsockopt(sock,IPPROTO_IP,IP_HDRINCL,(char*)&flag,sizeof(flag))==
SOCKET_ERROR)
{
printf("setsockopt IP_HDRINCL error!\n");
return false;
}
nTimeOver=1000;
if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char*)&nTimeOver, sizeof(nTimeOver))==SOCKET_ERROR)
{
printf("setsockopt SO_SNDTIMEO error!\n");
return false;
}
//填充IP首部
ipHeader.h_verlen=(IPVER<<4 | sizeof(ipHeader)/sizeof(unsigned long));
ipHeader.tos=(UCHAR)0;
ipHeader.total_len=htons((unsigned short)sizeof(ipHeader)+sizeof(tcpHeader)+sizeof(tcp_send_data));
ipHeader.ident=0; //16位标识
ipHeader.frag_and_flags=0; //3位标志位
ipHeader.ttl=128; //8位生存时间
ipHeader.proto=IPPROTO_UDP; //协议类型
ipHeader.checksum=0; //检验和暂时为0
ipHeader.sourceIP=inet_addr(argv[1]); //32位源IP地址
ipHeader.destIP=inet_addr(argv[3]); //32位目的IP地址
//计算IP头部检验和
memset(check_Buff,0,MAX_BUFF_LEN);
memcpy(check_Buff,&ipHeader,sizeof(IP_HEADER));
ipHeader.checksum=checksum(check_Buff,sizeof(IP_HEADER));
//构造TCP伪首部
psdHeader.saddr=ipHeader.sourceIP;
psdHeader.daddr=ipHeader.destIP;
psdHeader.mbz=0;
psdHeader.ptcl=ipHeader.proto;
psdHeader.tcpl=htons(sizeof(TCP_HEADER)+sizeof(tcp_send_data));
//填充TCP首部
tcpHeader.th_dport=htons(atoi(argv[4])); //16位目的端口号
tcpHeader.th_sport=htons(atoi(argv[2])); //16位源端口号
tcpHeader.th_seq=0; //SYN序列号
tcpHeader.th_ack=0; //ACK序列号置为0
//TCP长度和保留位
tcpHeader.th_lenres=(sizeof(tcpHeader)/sizeof(unsigned long)<<4|0);
tcpHeader.th_flag=2; //修改这里来实现不同的标志位探测,2是SYN,1是//FIN,16是ACK探测 等等
tcpHeader.th_win=htons((unsigned short)16384); //窗口大小
tcpHeader.th_urp=0; //偏移大小
tcpHeader.th_sum=0; //检验和暂时填为0
//计算TCP校验和
memset(check_Buff,0,MAX_BUFF_LEN);
memcpy(check_Buff,&psdHeader,sizeof(psdHeader));
memcpy(check_Buff+sizeof(psdHeader),&tcpHeader,sizeof(tcpHeader));
memcpy(check_Buff+sizeof(PSD_HEADER)+sizeof(TCP_HEADER),
tcp_send_data,sizeof(tcp_send_data));
tcpHeader.th_sum=checksum(check_Buff,sizeof(PSD_HEADER)+
sizeof(TCP_HEADER)+sizeof(tcp_send_data));
//填充发送缓冲区
memset(Sendto_Buff,0,MAX_BUFF_LEN);
memcpy(Sendto_Buff,&ipHeader,sizeof(IP_HEADER));
memcpy(Sendto_Buff+sizeof(IP_HEADER),&tcpHeader,
sizeof(TCP_HEADER));
memcpy(Sendto_Buff+sizeof(IP_HEADER)+sizeof(TCP_HEADER),
tcp_send_data,sizeof(tcp_send_data));
int datasize=sizeof(IP_HEADER)+sizeof(TCP_HEADER)+
sizeof(tcp_send_data);
//发送数据报的目的地址
SOCKADDR_IN dest;
memset(&dest,0,sizeof(dest));
dest.sin_family=AF_INET;
dest.sin_addr.s_addr=inet_addr(argv[3]);
dest.sin_port=htons(atoi(argv[4]));
rect=sendto(sock,Sendto_Buff,datasize, 0,(struct sockaddr*)&dest, sizeof(dest));
if (rect==SOCKET_ERROR)
{
printf("send error!:%d\n",WSAGetLastError());
return false;
}
else
printf("send ok!\n");
closesocket(sock);
WSACleanup();
return 1;
}
实验运行结果:
所以进入dos,并进入到该可执行文件的目录下后在命令提示行下输入:sendtcp 192.168.18.3 12 192.168.18.7 25,回车运行,运行截图如下:
本实验小结:
在对TCP数据包头部进行填充时,首先需要我们去充分了解它的数据结构,在这个过程中可以了解相应字节上应该存放的内容和它们的功能。由于本次的课程设计只要求填充一个TCP数据包,然后将其发送出去,使用的数据发送函数是sendto(),成功地完成sendto()调用只能保证数据已经从本地发送出去,并不意味着数据传送到达目的地。
课程设计总结
在此次的计算机网络课程设计中,我们一共做了三个程序设计的实验,帧封装、IP数据包解析和发送TCP数据包。
在编写程序的过程中,用到了很多的函数,这些函数的运用使得程序简便而且正确的运行出来。为了正确的实现这些函数,查阅了很多相关的资料,从中获得了大量的有用的信息,收获也颇丰富。
在这次的课程设计中,动手能力得到了很大的提高,而且将这学期所学的网络的知识和以前所学的编程的知识充分的联系起来,对这门课的认识又提高了一层。
展开阅读全文