#ifndef TESTTCP_H #define TESTTCP_H #include <endian.h> #pragma pack(1) //ip协议头 struct IPHeader { unsigned char headerLen:4; unsigned char version:4; unsigned char tos; //服务类型 unsigned short totalLen; //总长度 unsigned short id; //标识 unsigned short flagOffset; //3位标志+13位片偏移 unsigned char ttl; //TTL unsigned char protocol; //协议 unsigned short checksum; //首部检验和 unsigned int srcIP; //源IP地址 unsigned int dstIP; //目的IP地址 }; //TCP首部 struct TCPHeader { unsigned short srcPort; //源端口 unsigned short dstPort; //目的端口 //封包序号 如果 TCP 数据太大时(大于 IP 封包的容许程度), 就得要进行分段.这个 Sequence Number 就是记录每个封包的序号, //可以让收受端重新将 TCP 的数据组合起来。序号字段的值则指的是本报文段所发送的数据的第一个字节的序号 unsigned int seqNum; //确认号 为了确认主机端确实有收到我们 client 端所送出的封包数据,我们 client 端当然希望能够收到主机方面的响应,那就是这个 Acknowledge Number 的用途了 //当 client 端收到这个确认码时,就能够确定之前传递的封包已经被正确的收下了.这个号是期望收到对方的下一个报文段的数据的第一个字节的序号 unsigned int ackNum; #if LITTLE_ENDIAN unsigned char reserved1:4; //保留6位中的4位首部长度 unsigned char headerLen:4; //tcp头部长度 //flags unsigned char fin:1; // 用来释放一个连接。当FIN=1时表示要求释放连接。 unsigned char syn:1; //同步当SYN=1时,表示这是一个连接请求或连接接受报文。 unsigned char rst:1; //复位,当TCP连接中出现了严重差错,必须释放连接。 unsigned char psh:1; //当两个应用程序进行通信时,当PSH=1时,表示尽快地用“推送”给应用程序,而不用等到缓冲区满了再向上交付。 unsigned char ack:1; //当ACK=1时,确认字段有效,在连接建立后的所有报文段都必须把ACK置为1。 unsigned char urg:1; //紧急指针,当URG=1时,表示紧急指针有效,应该尽快传送。用来处理避免TCP数据流中断 unsigned char reseverd2:2; //保留6位中的2位 #else unsigned char headerLen:4; //tcp头部长度 unsigned char reserved1:4; //保留6位中的4位首部长度 unsigned char reseverd2:2; //保留6位中的2位 unsigned char urg:1; //当URG=1时,表示紧急指针有效,应该尽快传送。 unsigned char ack:1; //当ACK=1时,确认字段有效,在连接建立后的所有报文段都必须把ACK置为1。 unsigned char psh:1; //当两个应用程序进行通信时,当PSH=1时,表示尽快地用“推送”给应用程序,而不用等到缓冲区满了再向上交付。 unsigned char rst:1; //复位,当TCP连接中出现了严重差错,必须释放连接。 unsigned char syn:1; //同步当SYN=1时,表示这是一个连接请求或连接接受报文。 unsigned char fin:1; // 用来释放一个连接。当FIN=1时表示要求释放连接。 #endif unsigned short windownSize; //16位窗口大小 unsigned short checksum; //16位TCP检验和 unsigned short urgPtr; //16位紧急指针 当URG=1时.表示紧急指针有效.应该尽快传送,不要按本来的列队次序传送 }; //TCP伪首部 struct PseudoHeader { unsigned int srcIP; //源地址 unsigned int dstIP; //目的地址 unsigned char mustBeZero;//置空,用于填充对齐 unsigned char protocol; //协议类型 unsigned short len; //TCP长度 }; //Max Segment Size最大segment长度 struct MSSOption { unsigned char kind; unsigned char length; unsigned short maxValue; }; //Selective Acknowledgment struct SACKOption { unsigned char kind; unsigned char length; }; //rtt即等于现在的时间tcp_time_stamp减去Timestamp Echo Reply struct TimestampsOption { unsigned char kind; unsigned char length; unsigned int timestamp; unsigned int timestampReply; }; struct NoOperation { unsigned char type; }; struct WindowScaleOption { unsigned char kind; unsigned char length; unsigned char shiftCount; }; #pragma pack() #endif // TESTTCP_H
testTcp.cpp
#include "testTcp.h" #include <iostream> #include <stdio.h> #include <string.h> #include <arpa/inet.h> #include <netinet/ip.h> #include <errno.h> using namespace std; int g_tcpSeqNum = 111; int g_tcpAckNum = 0; //ip数字转字符串 void ipLLToStr(long long ip_num,char* ip_str) { unsigned int iptok1 = (ip_num & 0xFF000000) >> 24; unsigned int iptok2 = (ip_num & 0x00FF0000) >> 16; unsigned int iptok3 = (ip_num & 0x0000FF00) >> 8; unsigned int iptok4 = ip_num & 0x000000FF; char ip[32]; bzero(ip,sizeof(ip)); snprintf(ip,sizeof(ip),"%d.%d.%d.%d",iptok1,iptok2,iptok3,iptok4); strcpy(ip_str,ip); } //打印ip头 void printIPHeader(IPHeader* ipHeader) { char srcIPStr[64] = "",dstIPStr[64]=""; ipLLToStr(ntohl(ipHeader->srcIP),srcIPStr); ipLLToStr(ntohl(ipHeader->dstIP),dstIPStr); int totalLen = ntohs(ipHeader->totalLen); char ipHeaderStr[256] = ""; snprintf(ipHeaderStr,sizeof(ipHeaderStr),"ip header: version:%d,tos:%d,protocol:%d,ttl:%d,srcIP:%s,dstIP:%s,totalLen:%d" ,ipHeader->version,ipHeader->tos,ipHeader->protocol,ipHeader->ttl,srcIPStr,dstIPStr,totalLen); cout << ipHeaderStr << endl; } //打印tcp头 void printTCPHeader(TCPHeader* tcpHeader) { char tcpHeaderStr[256] = ""; snprintf(tcpHeaderStr,sizeof(tcpHeaderStr),"tcp header:srcPort:%d,dstPort:%d,seqNum:%u,ackNum:%u,headerLen:%d,fin:%d,syn:%d,rst:%d" ",psh:%d,ack:%d,urg:%d,windowsSize:%d,checksum:%d,urgPtr:%d",ntohs(tcpHeader->srcPort),ntohs(tcpHeader->dstPort) ,ntohl(tcpHeader->seqNum),ntohl(tcpHeader->ackNum),tcpHeader->headerLen<<2,tcpHeader->fin,tcpHeader->syn,tcpHeader->rst ,tcpHeader->psh,tcpHeader->ack,tcpHeader->urg,ntohs(tcpHeader->windownSize),ntohs(tcpHeader->checksum),tcpHeader->urgPtr); cout << tcpHeaderStr << endl; } //构造IP头 void buildIPHeader(IPHeader* ipHeader,int totalLen,int srcIPNum,int dstIPNum) { ipHeader->headerLen = sizeof(IPHeader)>>2; ipHeader->version = IPVERSION; //服务类型 ipHeader->tos = 0; ipHeader->totalLen = htons(totalLen); ipHeader->id=htons(0); //设置flag标记为0 ipHeader->flagOffset=htons(0x02 << 13); ; //运用的协议为TCP协议 ipHeader->protocol=IPPROTO_TCP; //一个封包在网络上可以存活的时间 ipHeader->ttl=64; ipHeader->srcIP = srcIPNum; ipHeader->dstIP = dstIPNum; } //tcp和udp的校验和算法是相同的 unsigned short calChecksum(unsigned short* buffer, int size) { unsigned long cksum = 0; while(size>1) { cksum += *buffer++; size -= sizeof(unsigned short); } if(size){ cksum += *(unsigned char*)buffer; } cksum = (cksum>>16) + (cksum&0xffff); //将高16bit与低16bit相加 cksum += (cksum>>16); //将进位到高位的16bit与低16bit 再相加 return (unsigned short)(~cksum); } //构造tcp协议头 void buildTCPHeader(TCPHeader* tcpHeader,sockaddr_in* srcAddr,sockaddr_in* dstAddr,IPHeader* ipHeader,int headerLen,int seqNum,int ackNum,char syn,char ack) { tcpHeader->srcPort = srcAddr->sin_port; tcpHeader->dstPort = dstAddr->sin_port; tcpHeader->seqNum = htonl(seqNum); tcpHeader->ackNum = htonl(ackNum); tcpHeader->urg = 0; tcpHeader->ack = ack; tcpHeader->psh = 0; tcpHeader->rst = 0; tcpHeader->syn = syn; tcpHeader->fin = 0; tcpHeader->windownSize = htons(14600); tcpHeader->checksum = 0; tcpHeader->urgPtr = 0; tcpHeader->headerLen = headerLen>>2; //根据伪首部的buf计算ip头的校验和 char psdHeaderBuf[256] = ""; PseudoHeader* psdHeader = (PseudoHeader*)psdHeaderBuf; psdHeader->srcIP = ipHeader->srcIP; psdHeader->dstIP = ipHeader->dstIP; psdHeader->mustBeZero = 0; psdHeader->protocol = ipHeader->protocol; psdHeader->len = htons(sizeof(TCPHeader)); memcpy(psdHeaderBuf+sizeof(PseudoHeader),tcpHeader,sizeof(TCPHeader)); tcpHeader->checksum = calChecksum((unsigned short*)psdHeaderBuf, sizeof(PseudoHeader)+sizeof(TCPHeader)); } //发送syn int sendTcp(int sockfd,sockaddr_in* srcAddr,sockaddr_in* dstAddr,char syn,char ack,bool addMssOption,bool addSACKOption,bool addTsOption,bool addNoOption ,bool addWindowScale) { char buf[1024] = ""; int totalLen = sizeof(IPHeader) + sizeof(TCPHeader); if(addMssOption){ totalLen += sizeof(MSSOption); } if(addSACKOption){ totalLen += sizeof(SACKOption); } if(addTsOption){ totalLen += sizeof(TimestampsOption); } if(addNoOption){ totalLen += sizeof(NoOperation); } if(addWindowScale){ totalLen += sizeof(WindowScaleOption); } int pos = 0; IPHeader* ipHeader = (IPHeader*)buf; buildIPHeader(ipHeader,totalLen,srcAddr->sin_addr.s_addr,dstAddr->sin_addr.s_addr); printIPHeader(ipHeader); pos += sizeof(IPHeader); TCPHeader* tcpHeader = (TCPHeader*)(buf+pos); buildTCPHeader(tcpHeader,srcAddr,dstAddr,ipHeader,totalLen-sizeof(IPHeader),g_tcpSeqNum++,g_tcpAckNum,syn,ack); printTCPHeader(tcpHeader); pos += sizeof(TCPHeader); if(addMssOption) { MSSOption* mssOption = (MSSOption*)(buf+pos); mssOption->kind = 2;//htons(2); mssOption->length = sizeof(MSSOption);//htons(4); mssOption->maxValue = htons(1460); pos += sizeof(MSSOption); } if(addSACKOption) { SACKOption* sackOption = (SACKOption*)(buf+pos); sackOption->kind = 4; sackOption->length = sizeof(SACKOption); pos += sizeof(SACKOption); } if(addTsOption) { TimestampsOption* tsOption = (TimestampsOption*)(buf+pos); tsOption->kind = 8; tsOption->length = sizeof(TimestampsOption); tsOption->timestamp = htonl(111); tsOption->timestampReply = 0; pos += sizeof(TimestampsOption); } if(addNoOption) { NoOperation* noOption = (NoOperation*)(buf+pos); noOption->type = 1; pos += sizeof(NoOperation); } if(addWindowScale) { WindowScaleOption* windowScaleOption = (WindowScaleOption*)(buf+pos); windowScaleOption->kind = 3; windowScaleOption->length = sizeof(WindowScaleOption); windowScaleOption->shiftCount = 6; } if(sendto(sockfd,buf,totalLen,0,(struct sockaddr *)dstAddr,sizeof(*dstAddr))<0){ perror("sendto error"); } return 0; } //解析syn+ack void parseSynAck(int sockfd,sockaddr_in* dstAddr) { char recvBuf[1024] = ""; socklen_t cliLen = sizeof(dstAddr); int recvLen = recvfrom(sockfd,recvBuf,sizeof(recvBuf),0,(sockaddr*)&dstAddr,&cliLen); if(recvLen<=0) { cout << "parse syc ack fail,recvLen:" << recvLen << endl; return; } int pos = 0; IPHeader* ipHeader = (IPHeader*)recvBuf; pos += sizeof(IPHeader); printIPHeader(ipHeader); TCPHeader* tcpHeader = (TCPHeader*)(recvBuf+pos); printTCPHeader(tcpHeader); g_tcpSeqNum = ntohl(tcpHeader->ackNum); } int main() { int sockfd = socket(AF_INET,SOCK_RAW,IPPROTO_TCP); if( sockfd < 0) { cout << strerror(errno) << endl; return -1; } char srcStr[32] = "172.16.96.52"; char dstStr[32] = "192.168.145.51"; int srcPort = 50852; int dstPort = 6699; sockaddr_in srcAddr; bzero(&srcAddr,sizeof(srcAddr)); srcAddr.sin_family=AF_INET; srcAddr.sin_addr.s_addr = inet_addr(srcStr); srcAddr.sin_port = htons(srcPort); sockaddr_in dstAddr; bzero(&dstAddr,sizeof(dstAddr)); dstAddr.sin_family=AF_INET; dstAddr.sin_addr.s_addr = inet_addr(dstStr); dstAddr.sin_port = htons(dstPort); //IPPROTO_TP说明用户自己填写IP报文 //IP_HDRINCL表示由内核来计算IP报文的头部校验和,和填充那个IP的id int on = 1; setsockopt(sockfd,IPPROTO_IP,IP_HDRINCL,&on,sizeof(on)); cout << "syn:" << endl; char syn = 1,ack = 0; sendTcp(sockfd,&srcAddr,&dstAddr,syn,ack,true,true,true,true,true); cout << "syn+ack:" << endl; parseSynAck(sockfd,&dstAddr); //ack // syn = 0,ack = 1; // sendTcp(sockfd,&srcAddr,&dstAddr,syn,ack,true,true,true,true,true); return 0; } syn: ip header: version:4,tos:0,protocol:6,ttl:64,srcIP:172.16.96.52,dstIP:192.168.145.51,totalLen:60 tcp header:srcPort:50852,dstPort:6699,seqNum:111,ackNum:0,headerLen:40,fin:0,syn:1,rst:0,psh:0,ack:0,urg:0,windowsSize:14600,checksum:59258,urgPtr:0 syn+ack: ip header: version:4,tos:0,protocol:6,ttl:62,srcIP:192.168.145.51,dstIP:172.16.96.52,totalLen:60 tcp header:srcPort:6699,dstPort:50852,seqNum:3363298119,ackNum:112,headerLen:40,fin:0,syn:1,rst:0,psh:0,ack:1,urg:0,windowsSize:14480,checksum:34955,urgPtr:0 //理论上在这我们应该发送一个ack,但实际上在发完syn后,操作系统内核收到syc+ack,它检查内核里的socket, //发现没有一个socket对应于这个包,于是自动回复rst,关闭连接,所以我们无法再回复ack,因为连接已断开