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


    一.目的:

    自己拼接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 };
    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)
                               
      为什么要有第三次确认? 这主要是为了防止已失效的连接请求报文段突然又传送到了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包没有问题。

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

  • 相关阅读:
    学习笔记 线程异步请求过程
    学习笔记 urllib
    学习笔记 requests + BeautifulSoup
    python3 kmp 字符串匹配
    python3:实现字符串的全排列(有重复字符)
    python3:实现字符串的全排列(无重复字符)
    python 贝叶斯算法
    knn算法
    python基础5
    python基础4
  • 原文地址:https://www.cnblogs.com/ruo-yu/p/4982969.html
Copyright © 2020-2023  润新知