一.目的:
自己拼接IP头,TCP头,计算效验和,将生成的报文用原始套接字发送出去。
若使用tcpdump能监听有对方服务器的包回应,则证明TCP报文是正确的!
二.数据结构:
TCP首部结构图:
struct tcphdr结构体定义:
1 struct tcphdr //在#include <netinet/tcp.h>中定义 2 { 3 u_int16_t source; //源端口 16位 4 u_int16_t dest; //目的端口 16位 5 u_int32_t seq; //序列号 32位 6 u_int32_t ack_seq; //确认号 32位 7 # if __BYTE_ORDER == __LITTLE_ENDIAN //若当前环境为小端字节序 8 u_int16_t res1:4; //“保留部分”的前4位 9 u_int16_t doff:4; //数据偏移 4位 10 u_int16_t fin:1; //fin 发送端完成任务 11 u_int16_t syn:1; //syn 同步序号用来发起一个连接。 12 u_int16_t rst:1; //rst 重建连接 13 u_int16_t psh:1; //psh 接收方应该尽快将这个报文段交给应用层 14 u_int16_t ack:1; //ack 确认序号有效 15 u_int16_t urg:1; //urg 紧急指针有效 16 u_int16_t res2:2; //“保留部分”的后两位 17 # elif __BYTE_ORDER == __BIG_ENDIAN 18 u_int16_t doff:4; 19 u_int16_t res1:4; 20 u_int16_t res2:2; 21 u_int16_t urg:1; 22 u_int16_t ack:1; 23 u_int16_t psh:1; 24 u_int16_t rst:1; 25 u_int16_t syn:1; 26 u_int16_t fin:1; 27 # else 28 # error "Adjust your <bits/endian.h> defines" 29 # endif 30 u_int16_t window; //窗口 16位 31 u_int16_t check; //检验和 16位 32 u_int16_t urg_ptr; //紧急指针 16位 33 };
其中:
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)。
为什么要有第三次确认? 这主要是为了防止已失效的连接请求报文段突然又传送到了B,因而产生错误.
四.代码实现:
1 /* 2 ============================================================================ 3 Name : test.c 4 Author : huh 5 Version : 6 Copyright : ---notice--- 7 Description : Hello World in C, Ansi-style 8 ============================================================================ 9 */ 10 11 #include <sys/types.h> 12 #include <sys/socket.h> 13 #include <stdio.h> 14 #include <netinet/in.h> 15 #include <arpa/inet.h> 16 #include <unistd.h> 17 #include <stdlib.h> 18 #include <string.h> 19 #include <netinet/ip_icmp.h> 20 #include <netinet/udp.h> 21 #include <netinet/tcp.h> 22 23 #define MAXLINE 1024*50 24 25 #define LOCAL_IP "192.168.11.104" //本主机IP 26 #define LOCAL_PORT 8600 //本主机定义端口 27 #define DEST_IP "115.239.211.112" //要测试的目的ip(此处为百度ip,以后可能发生变化) 28 #define DEST_PORT 80 //要测试的目的端口 29 30 struct udp_front //tcp(udp)伪首部结构体 31 { 32 uint32_t srcip; 33 uint32_t desip; 34 u_int8_t zero; 35 u_int8_t protocol; 36 u_int16_t len; 37 }; 38 39 u_int16_t in_chksum(u_int16_t *addr, int len); 40 u_int16_t tcp_check(char *sendbuf, int len, const struct udp_front front); 41 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); 42 43 int main() 44 { 45 int raw_sockfd; 46 int size = 1024*50; 47 char send_message[MAXLINE]; 48 struct sockaddr_in server_address; 49 //创建原始套接字 50 raw_sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP); 51 //创建套接字地址 52 bzero(&server_address,sizeof(server_address)); 53 server_address.sin_family = AF_INET; 54 server_address.sin_addr.s_addr = inet_addr(DEST_IP); 55 //设置套接字为随数据包含IP首部(设置这个选项后需要我们手动写入IP头) 56 setsockopt(raw_sockfd, IPPROTO_IP, IP_HDRINCL, &size, sizeof(size)); 57 58 bzero(&send_message, sizeof(send_message)); 59 //拼接完整的TCP数据包(IP头+TCP头+数据) 60 int mesg_len = make_message(send_message, MAXLINE, inet_addr(LOCAL_IP), LOCAL_PORT, inet_addr(DEST_IP), DEST_PORT); 61 //将IP数据包发送出去 62 sendto(raw_sockfd, send_message, mesg_len, 0, (struct sockaddr *)&server_address, sizeof(server_address)); 63 close(raw_sockfd); 64 return 0; 65 } 66 67 //拼接IP数据报 68 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) 69 { 70 char message[20]; //数据在这里并没有用,为空值 71 bzero(message, sizeof(message)); 72 //strcpy(message, "hello,world!"); 73 struct iphdr *ip; 74 ip = (struct iphdr *)sendbuf; 75 ip->ihl = sizeof(struct iphdr) >> 2; //首部长度 76 ip->version = 4; //ip协议版本 77 ip->tos = 0; //服务类型字段 78 ip->tot_len = 0; //总长度 79 ip->id = htons(10000); //id值 80 ip->frag_off = 0; 81 ip->ttl = 128; 82 ip->protocol = IPPROTO_TCP; 83 ip->check = 0; //内核会算相应的效验和 84 ip->saddr = src_ip; 85 ip->daddr = des_ip; 86 87 struct udp_front front; 88 front.srcip = src_ip; 89 front.desip = des_ip; 90 front.len = htons(20 + strlen(message)); 91 front.protocol = 6; 92 front.zero = 0; 93 94 struct tcphdr *tcp; 95 tcp = (struct tcphdr *)(sendbuf + sizeof(struct iphdr)); 96 bzero(tcp, sizeof(struct tcphdr *)); 97 tcp->source = htons(src_port); //源端口 98 tcp->dest = htons(des_port); //目的端口 99 tcp->seq = htonl(100000000); //随机生成的数 100 tcp->ack_seq = 0; //当ack置0的时候,ack_seq无所谓 101 102 tcp->doff = 5; //数据偏移(TCP头部字节长度/4) 103 tcp->res1 = 0; //保留字段(4位) 104 tcp->fin = 0; //..用来释放一个连接 105 tcp->syn = 1; //..表示这是一个连接请求 106 tcp->rst = 0; //..用来表示tcp连接是否出现严重差错 107 tcp->psh = 0; //..推送 108 tcp->ack = 0; //..表示是一个连接请求 109 tcp->urg = 0; //..紧急数据标志 110 tcp->res2 = 0; //保留字段(2位) 111 tcp->window = htons(65535); //初始窗口值设置 112 113 tcp->check = 0; 114 tcp->urg_ptr = 0; 115 116 tcp->check = 0; //效验和,效验整个tcp数据报 117 strcpy((sendbuf+20+20), message); //此处message为空 118 119 tcp->check = tcp_check((sendbuf+20), 20+strlen(message), front); 120 121 ip->tot_len = (20 + 20 + strlen(message)); //总长度 122 printf("ip->tot_len:%d ",ip->tot_len); 123 ip->check = in_chksum((unsigned short *)sendbuf, 20); 124 125 return (ip->tot_len); 126 } 127 128 //计算tcp(udp)效验和 129 unsigned short tcp_check(char *sendbuf, int len, const struct udp_front front) 130 { 131 char str[MAXLINE]; 132 bzero(&str, MAXLINE); 133 bcopy(&front, str, sizeof(front)); 134 bcopy(sendbuf, str+sizeof(front), len); 135 struct udp_front *ptr; 136 ptr = (struct udp_front *)str; 137 char *s; 138 s = (str+20); 139 return in_chksum((unsigned short *)str, sizeof(front)+len); 140 } 141 142 //效验和算法 143 uint16_t in_chksum(uint16_t *addr, int len) 144 { 145 int nleft = len; 146 uint32_t sum = 0; 147 uint16_t *w = addr; 148 uint16_t answer = 0; 149 //把ICMP报头二进制数据以2字节为单位累加起来 150 while (nleft > 1) 151 { 152 sum += *w++; 153 nleft -= 2; 154 } 155 if (nleft == 1) 156 { 157 *(unsigned char *)(&answer) = *(unsigned char *)w; 158 sum += answer; 159 } 160 sum = (sum>>16) + (sum&0xffff); 161 sum += (sum>>16); 162 answer = ~sum; 163 return answer; 164 }
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包没有问题。
注:图片来自谢希仁老师的《计算机网络》课件