005.TCP--拼接TCP头部IP头部,实现TCP三次握手的第一步(Linux,原始套接字)

时间:2023-03-08 16:23:03

一.目的:

自己拼接IP头,TCP头,计算效验和,将生成的报文用原始套接字发送出去。

若使用tcpdump能监听有对方服务器的包回应,则证明TCP报文是正确的!

二.数据结构:

TCP首部结构图:

005.TCP--拼接TCP头部IP头部,实现TCP三次握手的第一步(Linux,原始套接字)

struct tcphdr结构体定义:

 struct tcphdr  //在#include <netinet/tcp.h>中定义
{
u_int16_t source; //源端口 16位
u_int16_t dest; //目的端口 16位
u_int32_t seq; //序列号 32位
u_int32_t ack_seq; //确认号 32位
# if __BYTE_ORDER == __LITTLE_ENDIAN //若当前环境为小端字节序
u_int16_t res1:; //“保留部分”的前4位
u_int16_t doff:; //数据偏移 4位
u_int16_t fin:; //fin 发送端完成任务
u_int16_t syn:; //syn 同步序号用来发起一个连接。
u_int16_t rst:; //rst 重建连接
u_int16_t psh:; //psh 接收方应该尽快将这个报文段交给应用层
u_int16_t ack:; //ack 确认序号有效
u_int16_t urg:; //urg 紧急指针有效
u_int16_t res2:; //“保留部分”的后两位
# elif __BYTE_ORDER == __BIG_ENDIAN
u_int16_t doff:;
u_int16_t res1:;
u_int16_t res2:;
u_int16_t urg:;
u_int16_t ack:;
u_int16_t psh:;
u_int16_t rst:;
u_int16_t syn:;
u_int16_t fin:;
# else
# error "Adjust your <bits/endian.h> defines"
# endif
u_int16_t window; //窗口 16位
u_int16_t check; //检验和 16位
u_int16_t urg_ptr; //紧急指针 16位
};

struct tcphdr

其中:

1.TCP连接建立的第一步中,需要将SYN=1(SYN的报文段不能携带数据),ACK=0。

2.序列号(seq)的窗口(window)的值(几乎)是任意的

3.注意主机字节序和网络字节序之间的转换(大于1字节的数都需要处理)

三.TCP连接建立过程(3次握手) :

  如下图画出了TCP的建立连接的过程,假定主机A运行的是TCP客户程序,而B运行TCP服务器程序,最初两端的TCP进程都出于CLOSE(关闭)状态。图中在主机下面的方框分别是TCP进程所处的状态。请注意,A主动打开连接,而B被动打开连接。
以下连接过程叫做三次握手
  B的TCP服务器进程先创建传输控制块TCB,准备接受客户进程的连接请求。然后服务器进程就处于LISTEN(收听)状态,等待客户的连接请求。如有,即做出响应。 
  1.A的TCP客户进程也是首先创建传输控制模块TCB,然后向B发出连接请求报文段,这时首部中的同步位SYN=1,同时选择一个初始序号seq=x。SYN报文段不能携带数据,但要消耗掉一个序号。这时,TCP客户进程进入SYN-SENT(同步已发送)状态。 
  2.B收到连接          请求报文段后,如同意建立连接,则向A发送确认。在确认报文段中应把SYN位和ACK位都置1,确认号是ack=x+1,同时也为自己选择一个初始序号seq=y。这个报文段也不能携带数据,但同样要消耗掉一个序号。这时TCP服务器进程进入SYN-RCVD(同步收到)状态。 
  3.TCP客户进程收到B的确认后,还要向B给出确认。确认报文段的ACK置1,确认号ack=y+1,而自己的序号seq=x+1,ACK报文段可以携带数据,但如果不携带数据则不消耗序号,在这种情况下,下一个数据报文段的序号仍是seq=x+1。这时,TCP连接已经建立,A进入ESTABLISHED(已建立连接)状态。
  当B收到A确认后,也进入ESTABLISHED(已建立连接)状态,这个过程就是三次握手(three-way handshake)
                            005.TCP--拼接TCP头部IP头部,实现TCP三次握手的第一步(Linux,原始套接字)
  为什么要有第三次确认? 这主要是为了防止已失效的连接请求报文段突然又传送到了B,因而产生错误.

四.代码实现:

 /*
============================================================================
Name : test.c
Author : huh
Version :
Copyright : ---notice---
Description : Hello World in C, Ansi-style
============================================================================
*/ #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*50 #define LOCAL_IP "192.168.11.104" //本主机IP
#define LOCAL_PORT 8600 //本主机定义端口
#define DEST_IP "115.239.211.112" //要测试的目的ip(此处为百度ip,以后可能发生变化)
#define DEST_PORT 80 //要测试的目的端口 struct udp_front //tcp(udp)伪首部结构体
{
uint32_t srcip;
uint32_t desip;
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, uint32_t src_ip, u_int16_t src_port, uint32_t des_ip, u_int16_t des_port); int main()
{
int raw_sockfd;
int size = *;
char send_message[MAXLINE];
struct sockaddr_in server_address;
//创建原始套接字
raw_sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
//创建套接字地址
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, inet_addr(LOCAL_IP), LOCAL_PORT, inet_addr(DEST_IP), DEST_PORT);
//将IP数据包发送出去
sendto(raw_sockfd, send_message, mesg_len, , (struct sockaddr *)&server_address, sizeof(server_address));
close(raw_sockfd);
return ;
} //拼接IP数据报
int make_message(char *sendbuf, int send_buf_len, uint32_t src_ip, u_int16_t src_port, uint32_t des_ip, u_int16_t des_port)
{
char message[]; //数据在这里并没有用,为空值
bzero(message, sizeof(message));
//strcpy(message, "hello,world!");
struct iphdr *ip;
ip = (struct iphdr *)sendbuf;
ip->ihl = sizeof(struct iphdr) >> ; //首部长度
ip->version = ; //ip协议版本
ip->tos = ; //服务类型字段
ip->tot_len = ; //总长度
ip->id = htons(); //id值
ip->frag_off = ;
ip->ttl = ;
ip->protocol = IPPROTO_TCP;
ip->check = ; //内核会算相应的效验和
ip->saddr = src_ip;
ip->daddr = des_ip; struct udp_front front;
front.srcip = src_ip;
front.desip = des_ip;
front.len = htons( + strlen(message));
front.protocol = ;
front.zero = ; struct tcphdr *tcp;
tcp = (struct tcphdr *)(sendbuf + sizeof(struct iphdr));
bzero(tcp, sizeof(struct tcphdr *));
tcp->source = htons(src_port); //源端口
tcp->dest = htons(des_port); //目的端口
tcp->seq = htonl(); //随机生成的数
tcp->ack_seq = ; //当ack置0的时候,ack_seq无所谓 tcp->doff = ; //数据偏移(TCP头部字节长度/4)
tcp->res1 = ; //保留字段(4位)
tcp->fin = ; //..用来释放一个连接
tcp->syn = ; //..表示这是一个连接请求
tcp->rst = ; //..用来表示tcp连接是否出现严重差错
tcp->psh = ; //..推送
tcp->ack = ; //..表示是一个连接请求
tcp->urg = ; //..紧急数据标志
tcp->res2 = ; //保留字段(2位)
tcp->window = htons(); //初始窗口值设置 tcp->check = ;
tcp->urg_ptr = ; tcp->check = ; //效验和,效验整个tcp数据报
strcpy((sendbuf++), message); //此处message为空 tcp->check = tcp_check((sendbuf+), +strlen(message), front); ip->tot_len = ( + + strlen(message)); //总长度
printf("ip->tot_len:%d\n",ip->tot_len);
ip->check = in_chksum((unsigned short *)sendbuf, ); 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+);
return in_chksum((unsigned short *)str, sizeof(front)+len);
} //效验和算法
uint16_t in_chksum(uint16_t *addr, int len)
{
int nleft = len;
uint32_t sum = ;
uint16_t *w = addr;
uint16_t answer = ;
//把ICMP报头二进制数据以2字节为单位累加起来
while (nleft > )
{
sum += *w++;
nleft -= ;
}
if (nleft == )
{
*(unsigned char *)(&answer) = *(unsigned char *)w;
sum += answer;
}
sum = (sum>>) + (sum&0xffff);
sum += (sum>>);
answer = ~sum;
return answer;
}

tcpdump监听结果:

[root@huh ~]# tcpdump -nn -vvv tcp and host 192.168.11.104
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
13:24:10.154351 IP (tos 0x0, ttl 128, id 10000, offset 0, flags [none], proto TCP (6), length 40)
192.168.11.104.8600 > 115.239.211.112.80: Flags [S], cksum 0x9394 (correct), seq 100000000, win 65535, length 0
13:24:10.201365 IP (tos 0x0, ttl 128, id 18595, offset 0, flags [none], proto TCP (6), length 44)
115.239.211.112.80 > 192.168.11.104.8600: Flags [S.], cksum 0x5b2f (correct), seq 258283074, ack 100000001, win 64240, options [mss 1460], length 0
13:24:10.201444 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 40)
192.168.11.104.8600 > 115.239.211.112.80: Flags [R], cksum 0x9391 (correct), seq 100000001, win 0, length 0
^C
3 packets captured
3 packets received by filter
0 packets dropped by kernel

115.239.211.112:80端口的服务确实回应了,证明我们构造的TCP包没有问题。

注:图片来自谢希仁老师的《计算机网络》课件