分析TCP、IP头部
头部字段
IP头部结构
TCP头部结构
熟悉计算机网络的同学对这些都已经很熟悉了,就不复述了。
Tip: 为了结构清晰 分开的代码可能有一定逻辑性问题,请参照源码
构建TCP伪首部结构体
用于计算校验和
struct udp_front //tcp(udp)伪首部结构体
{
uint32_t srcip; //源IP
uint32_t desip; //目的IP
u_int8_t zero;
u_int8_t protocol; //协议类型
u_int16_t len; //协议长度
};
填充TCP伪首部
//...
struct udp_front front;
front.srcip = ip->saddr;
front.desip = ip->daddr;
front.len = htons(20 + strlen(message));
front.protocol = 6;
front.zero = 0;
填充IP头部
ip = (struct iphdr *)sendbuf;
ip->ihl = sizeof(struct iphdr) >> 2; //首部长度
ip->version = 4; //ip协议版本
ip->tos = 0; //服务类型字段
ip->tot_len = 0; //总长度
ip->id = htons(my_seq); //id值
ip->frag_off = 0;
ip->ttl = 128;
ip->protocol = IPPROTO_TCP;
ip->check = 0; //内核会算相应的效验和
// ip->saddr = src_ip;
//将一个点分十进制的IP转换成一个长整数型数
ip->saddr = inet_addr(argv[1]);
ip->daddr = inet_addr(argv[3]);
//...
ip->tot_len = (20 + 20 + strlen(message)); //IP头长度+TCP头部长度+数据长度 = 总长度
printf("ip->tot_len:%d\n",ip->tot_len);
ip->check = in_chksum((unsigned short *)sendbuf, 20);
填充TCP头部
TCP首部的syn、ack、fin等位 必须按照三次握手的要求构建,第一次请求syn位为1,响应ack位则为1...
struct tcphdr *tcp;
tcp = (struct tcphdr *)(sendbuf + sizeof(struct iphdr));
bzero(tcp, sizeof(struct tcphdr *));
//-------------------------------------------------------------------
//将短整型变量从主机字节顺序转变成网络字节顺序
//htonl 长
//atoi 把字符串转换成整型数
tcp->source = htons(atoi(argv[2])); //源端口
tcp->dest = htons(atoi(argv[4])); //目的端口
tcp->seq = htonl(100000000);
tcp->ack_seq = htonl(ack_seq);
tcp->doff = 5; //数据偏移(TCP头部字节长度/4)
tcp->res1 = 0; //保留字段(4位)
tcp->fin = 0; //..用来释放一个连接
tcp->syn = 1; //..表示这是一个连接请求
tcp->rst = 0; //..用来表示tcp连接是否出现严重差错
tcp->psh = 0; //..推送
tcp->ack = 0; //..表示是一个连接请求
tcp->urg = 0; //..紧急数据标志
tcp->res2 = 0; //保留字段(2位)
tcp->window = htons(65535); //初始窗口值设置
tcp->check = 0;
tcp->urg_ptr = 0;
tcp->check = 0; //效验和,效验整个tcp数据报
strcpy((sendbuf+20+20), message); //把message存入IP+TCP头部位之后
tcp->check = tcp_check((sendbuf+20), 20+strlen(message), front);
计算校验和
想要详细了解的请 参考 博客
//计算tcp(udp)效验和
unsigned short tcp_check(char *sendbuf, int len, const struct udp_front front)
{
char str[MAXLINE];
bzero(&str, MAXLINE);
bcopy(&front, str, sizeof(front));
bcopy(sendbuf, str+sizeof(front), len);
struct udp_front *ptr;
ptr = (struct udp_front *)str;
char *s;
s = (str+20);
return in_chksum((unsigned short *)str, sizeof(front)+len);
}
//效验和算法
uint16_t in_chksum(uint16_t *addr, int len)
{
int nleft = len;
uint32_t sum = 0;
uint16_t *w = addr;
uint16_t answer = 0;
//把ICMP报头二进制数据以2字节为单位累加起来
while (nleft > 1)
{
sum += *w++;
nleft -= 2;
}
if (nleft == 1)
{
*(unsigned char *)(&answer) = *(unsigned char *)w;
sum += answer;
}
sum = (sum>>16) + (sum&0xffff);
sum += (sum>>16);
answer = ~sum;
return answer;
}
握手过程细节
TCP需要三次握手建立连接之后才能传输数据,所以使用TCP原始套接字传输数据需要先完成三次握手: 第一次握手:客户端TCP头部 SYN位至为1 给定seq位的值 第二次握手:服务端返回SYN+ACK(0x012),ack_seq 为客户端第一次握手的seq位+1,并且会随机一个seq位值 第三次握手:客户端需要获取到服务端响应seq位的值,将其+1,存入TCP头部ack_seq位中,seq位同样+1
Tips:
- recvfrom获取的数据中,需要根据TCP首部位和IP首部位去判断所需字段在rev[]中的位置,已给出相关注释
ipheadlen = 4 * ipheadlen;这句代码 是因为IP首部的IP报头域的计算单位是4bytes ,所以需要将获取的值乘4才能得出最终结果20
//接收SYN + ACK报文
printf("receive SYN_ACK start:---------------------------------------\n"); //将接受的IP数据报输出
unsigned char rec[1024];
int n = recvfrom(raw_sockfd, rec, 1024, 0, NULL, NULL);
printf("receive %d bytes:\n", n); //将接受的IP数据报输出
for(int i=0; i<n; i++){
if(i % 16 == 0)
printf("\n");
printf("%02x ", rec[i]);
}
printf("\n\nreceive SYN_ACK end:---------------------------------------\n");
unsigned char ipheadlen = rec[0]; //取出IP数据包的长度
ipheadlen = (ipheadlen & 0x0f); //IP首部长度字段只占该字节后四位 将前四位全部至为0
printf("ipheadlen: %d\n",ipheadlen);
ipheadlen = 4 * ipheadlen; //IP报头域的数值表示整个IP报头的长度,单位是4bytes
//版本 4 + 头部长度 4 + 服务类型 8
unsigned short iplength = ntohs(*((unsigned short *)(rec+2))); //获取IP数据报总长度
printf("iplengthAll: %d\n",iplength);
unsigned short tcplength = iplength - ipheadlen; //计算TCP数据报长度
printf("TCPLen: %d\n",tcplength);
//ip头部 + 4(端口) + 4 (SEQ) ntohl 从网络字节顺序 转换为主机字节顺序
// SEQ
unsigned int seq = ntohs(*((signed int*)(rec+ipheadlen+4)));
printf("SEQ:%d\n",seq);
// 确认应答号
unsigned int ack = ntohs(*((signed int*)(rec+ipheadlen+8))); //获取TCP首部的确认号
printf("ACK:%d, %d\n", ack, my_seq);
//flag 位位置无法确定
unsigned char flag = rec[ipheadlen + 14]; //获取标志字段
printf("flag:%02x\n", flag);
//---------------------------------------------------------------------
// SYN + ACK 0x012
flag = (flag & 0x12); //只需要ACK和SYN标志的值 其他位都清0
if(flag != 0x12){ //判断是否为SYN+ACK包
printf("ACK+SYN\n");
}else{
printf("OK ACK+SYN\n");
//发送 ACK包
unsigned int ack_seq = ack;
int mesg_len_a = make_message_ACK(send_message, MAXLINE, argv, ack_seq);
sendto(raw_sockfd,send_message, mesg_len_a, 0, (struct sockaddr *)&server_address, sizeof(server_address));
}
实现效果
相关问题
- 使用原始套接字发送握手请求之后即使接收到了服务端的响应,Linux操作系统还是会因为TCP数据包未通过内核封装而直接发送RST中断连接
解决方式:
- 想办法过滤掉RST请求,让它无法发送出去,参考网上的解决方案
- 修改Linux源码(做不到,只剩下第一种方法)
源码
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/ip_icmp.h>
#include <netinet/udp.h>
#include <netinet/tcp.h>
#define MAXLINE 1024
#define LOCAL_IP "192.168.1.136" //本主机IP
#define LOCAL_PORT 8081 //本主机定义端口
#define DEST_IP "172.27.174.245" //要测试的目的ip
#define DEST_PORT 8080 //要测试的目的端口
struct udp_front //tcp(udp)伪首部结构体
{
uint32_t srcip; //源IP
uint32_t desip; //目的IP
u_int8_t zero;
u_int8_t protocol; //协议类型
u_int16_t len; //协议长度
};
u_int16_t in_chksum(u_int16_t *addr, int len);
u_int16_t tcp_check(char *sendbuf, int len, const struct udp_front front);
int make_message(char *sendbuf, int send_buf_len, char **argv);
int make_message_ACK(char *sendbuf, int send_buf_len, char **argv, unsigned int ack_seq);
int my_seq = 100; //TCP序号
int main(int argc, char **argv)
{
if (argc != 5) {
printf("Useage:SendTcp source_ip source_port dest_ip dest_port");
return -1;
}
int raw_sockfd;
int size = 1024;
char send_message[MAXLINE];
struct sockaddr_in server_address;
//创建原始套接字
raw_sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
//创建套接字地址
bzero(&server_address,sizeof(server_address));
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = inet_addr(DEST_IP);
//设置套接字为随数据包含IP首部(设置这个选项后需要我们手动写入IP头)
setsockopt(raw_sockfd, IPPROTO_IP, IP_HDRINCL, &size, sizeof(size));
bzero(&send_message, sizeof(send_message));
//拼接完整的TCP数据包(IP头+TCP头+数据)
int mesg_len = make_message(send_message, MAXLINE, argv);
printf("mesg_len:%d\n",mesg_len);
//for(int i = 40; i < 60; i++){
// printf("%c-",send_message[i]);
//}
//将IP数据包发送出去
sendto(raw_sockfd,send_message, mesg_len, 0, (struct sockaddr *)&server_address, sizeof(server_address));
//接收SYN + ACK报文
printf("receive SYN_ACK start:---------------------------------------\n"); //将接受的IP数据报输出
unsigned char rec[1024];
int n = recvfrom(raw_sockfd, rec, 1024, 0, NULL, NULL);
printf("receive %d bytes:\n", n); //将接受的IP数据报输出
for(int i=0; i<n; i++){
if(i % 16 == 0)
printf("\n");
printf("%02x ", rec[i]);
}
printf("\n\nreceive SYN_ACK end:---------------------------------------\n");
unsigned char ipheadlen = rec[0]; //取出IP数据包的长度
ipheadlen = (ipheadlen & 0x0f); //IP首部长度字段只占该字节后四位 将前四位全部至为0
printf("ipheadlen: %d\n",ipheadlen);
ipheadlen = 4 * ipheadlen; //IP报头域的数值表示整个IP报头的长度,单位是4bytes
//版本 4 + 头部长度 4 + 服务类型 8
unsigned short iplength = ntohs(*((unsigned short *)(rec+2))); //获取IP数据报总长度
printf("iplengthAll: %d\n",iplength);
unsigned short tcplength = iplength - ipheadlen; //计算TCP数据报长度
printf("TCPLen: %d\n",tcplength);
//ip头部 + 4(端口) + 4 (SEQ) ntohl 从网络字节顺序 转换为主机字节顺序
// SEQ
unsigned int seq = ntohs(*((signed int*)(rec+ipheadlen+4)));
printf("SEQ:%d\n",seq);
// 确认应答号
unsigned int ack = ntohs(*((signed int*)(rec+ipheadlen+8))); //获取TCP首部的确认号
printf("ACK:%d, %d\n", ack, my_seq);
//flag 位位置无法确定
unsigned char flag = rec[ipheadlen + 14]; //获取标志字段
printf("flag:%02x\n", flag);
//---------------------------------------------------------------------
// SYN + ACK 0x012
flag = (flag & 0x12); //只需要ACK和SYN标志的值 其他位都清0
if(flag != 0x12){ //判断是否为SYN+ACK包
printf("ACK+SYN\n");
}else{
printf("OK ACK+SYN\n");
//发送 ACK包
unsigned int ack_seq = ack;
int mesg_len_a = make_message_ACK(send_message, MAXLINE, argv, ack_seq);
sendto(raw_sockfd,send_message, mesg_len_a, 0, (struct sockaddr *)&server_address, sizeof(server_address));
}
//...
unsigned int ack_seq = ack;
int mesg_len_a = make_message_ACK(send_message, MAXLINE, argv, ack_seq);
sendto(raw_sockfd,send_message, mesg_len_a, 0, (struct sockaddr *)&server_address, sizeof(server_address));
close(raw_sockfd);
return 0;
}
//拼接IP数据报 回应ACK
int make_message_ACK(char *sendbuf, int send_buf_len, char **argv, unsigned int ack_seq)
{
//建立握手过程中无法传输数据
char message[]={"This is my homework of network of network,I am happy!"};
//bzero(message, sizeof(message));
//strcpy(message, "hello,world!");
printf("Data OK:->%s\n",message);
struct iphdr *ip;
ip = (struct iphdr *)sendbuf;
ip->ihl = sizeof(struct iphdr) >> 2; //首部长度
ip->version = 4; //ip协议版本
ip->tos = 0; //服务类型字段
ip->tot_len = 0; //总长度
ip->id = htons(my_seq + 1); //id值
ip->frag_off = 0;
ip->ttl = 128;
ip->protocol = IPPROTO_TCP;
ip->check = 0; //内核会算相应的效验和
// ip->saddr = src_ip;
//将一个点分十进制的IP转换成一个长整数型数
ip->saddr = inet_addr(argv[1]);
ip->daddr = inet_addr(argv[3]);
struct udp_front front;
front.srcip = ip->saddr;
front.desip = ip->daddr;
front.len = htons(20 + strlen(message));
printf("____________________________________DATA:%d_____________________________",strlen(message));
front.protocol = 6;
front.zero = 0;
struct tcphdr *tcp;
tcp = (struct tcphdr *)(sendbuf + sizeof(struct iphdr));
bzero(tcp, sizeof(struct tcphdr *));
//-------------------------------------------------------------------
//将短整型变量从主机字节顺序转变成网络字节顺序
//htonl 长
//atoi 把字符串转换成整型数
tcp->source = htons(atoi(argv[2])); //源端口
tcp->dest = htons(atoi(argv[4])); //目的端口
tcp->seq = htonl(100000001);
tcp->ack_seq = htonl(ack_seq + 1);
tcp->doff = 5; //数据偏移(TCP头部字节长度/4)
tcp->res1 = 0; //保留字段(4位)
tcp->fin = 0; //..用来释放一个连接
tcp->syn = 0; //..表示这是一个连接请求
tcp->rst = 0; //..用来表示tcp连接是否出现严重差错
tcp->psh = 0; //..推送
tcp->ack = 1; //..表示是一个连接请求
tcp->urg = 0; //..紧急数据标志
tcp->res2 = 0; //保留字段(2位)
tcp->window = htons(65535); //初始窗口值设置
tcp->check = 0;
tcp->urg_ptr = 0;
tcp->check = 0; //效验和,效验整个tcp数据报
strcpy((sendbuf+20+20), message); //把message存入IP+TCP头部位之后
tcp->check = tcp_check((sendbuf+20), 20+strlen(message), front);
ip->tot_len = (20 + 20 + strlen(message)); //IP头长度+TCP头部长度+数据长度 = 总长度
printf("ip->tot_len:%d\n",ip->tot_len);
ip->check = in_chksum((unsigned short *)sendbuf, 20);
return (ip->tot_len);
}
//拼接IP数据报 SYN第一次握手
int make_message(char *sendbuf, int send_buf_len, char **argv)
{
//建立握手过程中无法传输数据
char message[]={""};
//bzero(message, sizeof(message));
//strcpy(message, "hello,world!");
struct iphdr *ip;
ip = (struct iphdr *)sendbuf;
ip->ihl = sizeof(struct iphdr) >> 2; //首部长度
ip->version = 4; //ip协议版本
ip->tos = 0; //服务类型字段
ip->tot_len = 0; //总长度
ip->id = htons(my_seq); //id值
ip->frag_off = 0;
ip->ttl = 128;
ip->protocol = IPPROTO_TCP;
ip->check = 0; //内核会算相应的效验和
// ip->saddr = src_ip;
//将一个点分十进制的IP转换成一个长整数型数
ip->saddr = inet_addr(argv[1]);
ip->daddr = inet_addr(argv[3]);
struct udp_front front;
front.srcip = ip->saddr;
front.desip = ip->daddr;
front.len = htons(20 + strlen(message));
printf("____________________________________DATA:%d_____________________________",strlen(message));
front.protocol = 6;
front.zero = 0;
struct tcphdr *tcp;
tcp = (struct tcphdr *)(sendbuf + sizeof(struct iphdr));
bzero(tcp, sizeof(struct tcphdr *));
//-------------------------------------------------------------------
// TCP头部
//-------------------------------------------------------------------
//将短整型变量从主机字节顺序转变成网络字节顺序
//htonl 长
//atoi 把字符串转换成整型数
tcp->source = htons(atoi(argv[2])); //源端口
tcp->dest = htons(atoi(argv[4])); //目的端口
tcp->seq = htonl(100000000);
//tcp->ack_seq = 0; //当ack置0的时候,ack_seq无所谓
tcp->doff = 5; //数据偏移(TCP头部字节长度/4)
tcp->res1 = 0; //保留字段(4位)
tcp->fin = 0; //..用来释放一个连接
tcp->syn = 1; //..表示这是一个连接请求
tcp->rst = 0; //..用来表示tcp连接是否出现严重差错
tcp->psh = 0; //..推送
tcp->ack = 0; //..表示是一个连接请求
tcp->urg = 0; //..紧急数据标志
tcp->res2 = 0; //保留字段(2位)
tcp->window = htons(65535); //初始窗口值设置
tcp->check = 0;
tcp->urg_ptr = 0;
tcp->check = 0; //效验和,效验整个tcp数据报
strcpy((sendbuf+20+20), message); //把message存入IP+TCP头部位之后
tcp->check = tcp_check((sendbuf+20), 20+strlen(message), front);
ip->tot_len = (20 + 20 + strlen(message)); //IP头长度+TCP头部长度+数据长度 = 总长度
printf("ip->tot_len:%d\n",ip->tot_len);
ip->check = in_chksum((unsigned short *)sendbuf, 20);
return (ip->tot_len);
}
//计算tcp(udp)效验和
unsigned short tcp_check(char *sendbuf, int len, const struct udp_front front)
{
char str[MAXLINE];
bzero(&str, MAXLINE);
bcopy(&front, str, sizeof(front));
bcopy(sendbuf, str+sizeof(front), len);
struct udp_front *ptr;
ptr = (struct udp_front *)str;
char *s;
s = (str+20);
return in_chksum((unsigned short *)str, sizeof(front)+len);
}
//效验和算法
uint16_t in_chksum(uint16_t *addr, int len)
{
int nleft = len;
uint32_t sum = 0;
uint16_t *w = addr;
uint16_t answer = 0;
//把ICMP报头二进制数据以2字节为单位累加起来
while (nleft > 1)
{
sum += *w++;
nleft -= 2;
}
if (nleft == 1)
{
*(unsigned char *)(&answer) = *(unsigned char *)w;
sum += answer;
}
sum = (sum>>16) + (sum&0xffff);
sum += (sum>>16);
answer = ~sum;
return answer;
}