发送TCP数据包

一、  课程设计目的

TCP(传输控制协议)是一种面向连接的、可靠的传输层协议。TCP协议在网络层IP协议的基础上,向应用层用户进程提供可靠的、全双工的数据流传输。本课程设计的目的就是设计一个发送TCP数据包的程序,并根据本设计说明TCP数据包的结构以及TCP协议与IP协议的关系,使学生对TCP协议的工作原理有更深入的认识。

二、课程设计要求

本程序的功能是填充一个TCP数据包,并发送给目的主机。

1.以命令行形式运行:SendTCP source_ip source_port dest_ip dest_port

其中,SendTCP为程序名; source_ip为源端IP地址;source_port为源端口;dest_ip为目的IP地址;dest_port为目的端口。

2.其他的TCP头部参数请自行设定。

3.数据字段为“This is my homework of network,I am happy!”

4.成功发送后在屏幕上输出“send OK”

三、课程设计设备和环境

硬件坏境:PC微机

软件环境:windows 2000 microsoft Visual C++ 6.0

四、相关知识发送TCP数据包 - 亦秋之心 - 亦秋之心

编制本程序前,要对TCP协议有一定的了解。当应用进程有报文需要通过TCP发送时, 它就将此应用层报文传送给执行TCP协议的传输实体。TCP传输实体将用户数据加上TCP报头,形成TCP数据包,在TCP数据包上增加IP头部,形成IP包。图11-1显示的是TCP数据包和IP包的关系。

 

 

TCP协议的数据传输单元称为报文段,其格式如图11-2所示。报文段报头的长度为20B-60B。其中固定长度是20B,选项部分长度最多为40BTCP报文段主要包括以下字段。

l       端口号:端口号字段包括源端口号和目的端口号。每个端口号的长短是16位,分别表示发送该TCP包的应用进程的端口号和接受该TCP包的应用进程的端口号。

l       序号:长度为32位。由于TCP协议时面向数据流的,它所传送的报文段可以视为连续的数据流,因此需要给每一个字节编号。序号字段的序号指的是本报文段数据的第一个字节的顺序号。

发送TCP数据包 - 亦秋之心 - 亦秋之心

 

l       确认号:该字段的长度为32位,它表示接收端希望接收的下一个TCP包的第一个字节的序号。

l       报文长度:该字段长度为4位。TCP报头长度是以4B为一个单元来计算的,实际上报头长度子20B-60B之间。因此这个字段的值在5-15之间。

l       保留:长度为6位,留做今后使用,目前全部置0.

l       控制:这个字段定义了6中不同的标志,每个标志占一位,在同一时间可以设置一位或多位。URG位为1时,表明有需要紧急处理的数据。ACK位为1时,表明确认号的字段有效。PSH位为1时,表明应尽快把数据传送给应用程序,否则表明允许数据在缓存中存放一段时间。PST位为1时,表明要强制切断连接。SYN位为1时,表明有确立连接的请求,这时,把序号字段的初始值作为序号字段的值,以便开始通信。FIN位为1时,表明发送方已经没有数据发送了。

l       窗口大小:长度为16位,窗口对应的数据是以字节为单位的数据,因此最多能够传送的数据为65535B

l       紧急指针:该字段长度为16位,指向必须紧急处理的数据的位置,只有当标志URG=1时紧急指针才有效。从TCP报头后面的报文数据开始,到紧急指针所指出长度的数据,就是必须紧急处理的数据。

l       选项:该字段可以多达40B,包括单字节选项和多字节选项。

l       校验和:该字段长度为16位,校验和的校验范围包括伪头部、TCP报头以及应用层来的数据。其计算方法与IP协议头部校验和的计算方法一样(参考7.3节)。伪头部为12B,它本身并不是TCP数据包的真正头部,只是在计算校验和时,临时和TCP数据包连接在一起。伪头部的格式如图11-3所示。

发送TCP数据包 - 亦秋之心 - 亦秋之心

 

五、  课程设计分析

本课程设计的目标是发送一个TCP包,我们利用原始套接字来完成这个工作。整个程序由初始化原始套接字和填充发送TCP包两个部分组成。下面结合核心代码对程序的具体实现进行讲解,同时为使程序流程更加清晰,去掉了错误检查等保护性代码。

1)   使用原始套接字

一种实现TCP数据报发送的常见方法是利用原始套接字,创建原始套接字的代码如下:

SOCKET sockRaw ;

sockRaw=WSASocket(AF_INET,SOCK_RAW,IPPROTO_RAW,NULL,0,WSA_FLAG_OVERLAPPED);

这里,我们设置了SOCK_RAW标志,表示我们声明的是一个原始套接字类型。为了使用发送接收超时设置(设置SO_RCVTIMEOSO_SNDTIMEO,必须将标志位置为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。如果没有这个键值,就添加上。同时,我们还要设置发送超时。

boolean Flag=TRUE;

setsockopt(sockRaw,IPPROTO_IP,IP_HDRINCL,(char*)&Flag,sizeof(Flag));

int timeout=1000;

setsockopt(sockRaw,SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout,sizeof(timeout));

在这里,我使用基本套接字SOL_SOCKET,设置SO_SNDTIMEO表示使用发送超时设置,超时时间设置为1000ms

2)   定义IP头部、TCP头部和伪头部的数据结构

除了要定义IP头部和TCP头部的数据结构用于数据包填充之外,我们还需要定义一个TCP伪头部的数据结构来计算校验码。这三个数据结构的第一如下:

//定义ip头部结构体

typedef struct _iphdr

{

UCHAR h_verlen;       //4IP版本号,4位首部长度

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;      //16IP首部校验和

ULONG sourceIP;        //32位源IP地址

ULONG destIP;          //332位目的IP地址

}IP_HEADER;

//定义TCP伪头部

typedef struct _psdhdr

{

ULONG saddr;           //源地址

ULONG daddr;           //目的地址

UCHAR mbz;             //没用

UCHAR ptcl;              //协议类型

USHORT tcpl;             //TCP长度

}PSD_HEADER;

//定义TCP头部结构

typedef struct _tcphdr

{

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;

3)   填充数据包

在填充数据包的过程中,我们不仅要填充TCP头部还要填充IP头部。同时,为了填充校验和,我们还要对TCP伪头部进行填充。部分核心代码如下:

#define IPVER 4                      //IP协议预定

#define MAX_BUFF_LEN 65500        //发送缓存区最大值

IP_HEADER ip_header={0};            //IP首部结构

PSD_HEADER psd_header={0};         //TCP伪首部结构

TCP_HEADER tcp_header={0};         //TCP首部结构

char Sendto_Buff[MAX_BUFF_LEN];    //发送缓存区

unsigned short check_Buff[MAX_BUFF_LEN]; //校验和缓存区

const char tcp_send_data[]={"This is my homework of network,I am happy!"};

//填充IP首部

/*版本号*/

ip_header.h_verlen=(IPVER<<4|sizeof(ip_header)/sizeof(unsigned long));

ip_header.tos=(UCHAR)0;                    //服务类型

/*16位总长度(字节)*/

ip_header.total_len=htons((unsigned short)(sizeof(IP_HEADER)

+sizeof(TCP_HEADER)+sizeof(tcp_send_data)));

ip_header.ident=0;                    //16位标识

ip_header.frag_and_flags=0;            //3位标志位

ip_header.ttl=(UCHAR)128;            //8位生存时间

ip_header.proto=IPPROTO_TCP;        //协议类型

ip_header.checksum=0;                //校验和暂存时填充为0

ip_header.sourceIP=inet_addr(argv[1]);    //32位源IP地址

ip_header.destIP=inet_addr(argv[3]);      //32位目的IP地址

 

//计算IP头部校验和

memset(check_Buff,0,MAX_BUFF_LEN);

memcpy(check_Buff,&ip_header,sizeof(IP_HEADER));

ip_header.checksum=checksum(check_Buff,sizeof(IP_HEADER));

 

//构造TCP伪首部

psd_header.saddr=ip_header.sourceIP;    //源地址

psd_header.daddr=ip_header.destIP;      //目的地址

psd_header.mbz=0;

psd_header.ptcl=ip_header.proto;        //直接取上面的协议

psd_header.tcpl=htons(sizeof(TCP_HEADER)+sizeof(tcp_send_data));

 

//填充TCP首部

tcp_header.th_sport=htons((unsigned short)atoi(argv[2]));

//16位源端口号

tcp_header.th_dport=htons((unsigned short)atoi(argv[4]));

//16位目的端口号

tcp_header.th_seq=0;                //SYN标志

tcp_header.th_ack=0;                //ACK序列号置为0

/*TCP长度和保留字*/

tcp_header.th_lenres=(sizeof(TCP_HEADER)/sizeof(unsigned long)<<4|0);

tcp_header.th_flag=(UCHAR)2;                //SYN标志

tcp_header.th_win=htons((unsigned short)16384);  //窗口大小

tcp_header.th_urp=0;                         //偏移

tcp_header.th_sum=0;                     //校验和暂时填为0

 

//计算机TCP校验和

memset(check_Buff,0,MAX_BUFF_LEN);

memcpy(check_Buff,&psd_header,sizeof(PSD_HEADER));

memcpy(check_Buff+sizeof(PSD_HEADER),&tcp_header,sizeof(TCP_HEADER));

memcpy(check_Buff+sizeof(PSD_HEADER)+sizeof(TCP_HEADER),tcp_send_data,sizeof(tcp_send_data));

tcp_header.th_sum=checksum(check_Buff,sizeof(PSD_HEADER)+sizeof(TCP_HEADER)+sizeof(tcp_send_data));

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);

 }

4)   发送数据包

填充完TCP包后,最后的工作就是把数据包发送出去。以下代码完成三个工作:填充发送缓冲区,建立目的主机的sockaddr_in结构,调用sendto函数完成发送功能。

//填充发送缓冲区

memset(Sendto_Buff,0,MAX_BUFF_LEN);

memcpy(Sendto_Buff,&ip_header,sizeof(IP_HEADER));

memcpy(Sendto_Buff+sizeof(IP_HEADER),&tcp_header,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]);//填入目标机器的IP地址

 

//发送

int bwrote=sendto(sockRaw,Sendto_Buff,datasize,0,(struct sockaddr*)&dest,sizeof(dest));

5)   程序流程

11-4给出本程序的流程框图。

 

发送TCP数据包 - 亦秋之心 - 亦秋之心

 

六、  实验步骤及结果

1.       E盘中的实现文件SendTCP.cppmicrosoft Visual C++ 6.0软件编译运行。

2..步骤1编译运行无误后,在DOS命令窗口命令行中输入“E:”命令,回车后,继续在命令行中输入“cd debug”命令,并按回车。

3.DOS命令窗口的命令行中继续输入“SendTCP”命令,并按回车。

4.DOS命令行中继续输入“SendTCP 115.155.135.142 12 115.135.155.158 25”命令,按回车后,得到的运行结果如下:

 

发送TCP数据包 - 亦秋之心 - 亦秋之心

 

七、课程设计总结

在对TCP数据包头部进行填充时,首先需要我们去充分了解它的数据结构,在这个过程中可以了解相应字节上应该存放的内容和它们的功能。由于本次的课程设计只要求填充一个TCP数据包,然后将其发送出去,使用的数据发送函数是sendto()成功地完成sendto()调用只能保证数据已经从本地发送出去,并不意味着数据传送到达目的地。

八、      相关扩展

C/C++相比,Java语言在网路编程方面显得更加方便简洁,在这里我们给出Java语言实现设计要求的核心代码。

class SendTCP

{

  public static void main(String[] args)

{

 if(args.lenth!=4)

{

   System.out.println(“Your input is Wrong Try again!”);

   System.ecit(0);

}

Try

{

   String str=new String(“This is my homework of network,I am happy!”);

   Socket socketObj=new Socket(args[2],Integer.parseInt(args[3]));

OutputStream os=socketOnj.getOutputStream();

OutputStream Write osw=new OutputStream Writer(os);

Osw.writer(str);

Osw.flush();

 

socketObj.close();

System.out.println(“Send OK!”);

       }

       Catch(IOException e)

       {

         System.out.println(“Sorry,can’t connect to ”+args[0]+” :”+args[3]);

          System.out.println(e);

}

}

}

 

  附录:【能够直接执行的,不需要修改】

//实现文件:SendTCP.cpp

#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位首部长度,4IP版本号

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;        //16IP首部校验和

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; //修改这里来实现不同的标志位探测,2SYN1//FIN16ACK探测 等等

    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;

}

转自:http://landeqiao.blog.163.com/blog/static/690907562010111593847321/

posted on 2011-03-01 19:13  意扬  阅读(6428)  评论(0)    收藏  举报

导航