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

005.TCP--拼接TCP头部IP头部,实现TCP三次握手的第一步(Linux,原始套接字) - ruo_yu - 博客园一.目的:自己拼接IP头,TCP头,计算效验和,将生成的报文用原始套接字发送出去。若使用tcpdump能监听有对方服务器的包回应,则证明TCP报文是正确的!二.数据结构:TCP首部结构图:structhttps://www.cnblogs.com/ruo-yu/p/4982969.html

一.目的

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

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

二.数据结构

TCP首部结构图:

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;   //“保留部分”的前4位
    u_int16_t doff:4;   //数据偏移 4位
    u_int16_t fin:1;    //fin  发送端完成任务
    u_int16_t syn:1;    //syn  同步序号用来发起一个连接。
    u_int16_t rst:1;    //rst  重建连接
    u_int16_t psh:1;    //psh  接收方应该尽快将这个报文段交给应用层
    u_int16_t ack:1;    //ack  确认序号有效
    u_int16_t urg:1;    //urg  紧急指针有效
    u_int16_t res2:2;   //“保留部分”的后两位
#  elif __BYTE_ORDER == __BIG_ENDIAN
    u_int16_t doff:4;
    u_int16_t res1:4;
    u_int16_t res2:2;
    u_int16_t urg:1;
    u_int16_t ack:1;
    u_int16_t psh:1;
    u_int16_t rst:1;
    u_int16_t syn:1;
    u_int16_t fin:1;
#  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位
};

 其中:

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,因而产生错误.

四.代码实现

/*
 ============================================================================
 Name        : build_tcp_packet.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 pseudo_hdr  //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 pseudo_hdr 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);

/* 按照wireshark hex dump 格式打印数据包,使用wireshark 验证数据包是否正确*/
void hex_dump(unsigned char *packet, int len)
{
    int i = 0;
    
    printf("hex dump:");
    for (i = 0; i < len; i++)
    {
        if (i % 16 == 0)
        {
            printf("\n%04x ", i);
        }
        printf("%02x ", packet[i]);
    }
    printf("\n");
}

void c_array_dump(unsigned char *packet, int len)
{
    int i = 0;
    
    printf("\nc array dump:\n");
    printf("static const unsigned char pkt1[] = {");
    for (i = 0; i < len; i++)
    {
        if (i % 16 == 0)
        {
            printf("\n");
        }
        printf("0x%02x, ", packet[i]);
    }
    printf("\n};\n");
}

int main()
{
    int raw_sockfd;
    int size = 1024*50;
    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, 0, (struct sockaddr *)&server_address, sizeof(server_address));
    close(raw_sockfd);
    
    hex_dump(send_message, mesg_len);
    c_array_dump(send_message, mesg_len);

    return 0;
}
 
//拼接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[20];   //数据在这里并没有用,为空值
    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(10000);   //id值
    ip->frag_off = 0;
    ip->ttl = 128;
    ip->protocol = IPPROTO_TCP;
    ip->check = 0;  //内核会算相应的效验和
    ip->saddr = src_ip;
    ip->daddr = des_ip;
 
    struct pseudo_hdr front;
    front.srcip = src_ip;
    front.desip = des_ip;
    front.len = htons(20 + strlen(message));
    front.protocol = 6;
    front.zero = 0;
 
    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(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为空
 
    tcp->check = tcp_check((sendbuf+20), 20+strlen(message), front);
 
    ip->tot_len = htons(20 + 20 + strlen(message));   //总长度
    printf("ip->tot_len:%d\n",ntohs(ip->tot_len));
    ip->check = in_chksum((unsigned short *)sendbuf, 20);
 
    return ntohs(ip->tot_len);
}
 
//计算tcp(udp)效验和
unsigned short tcp_check(char *sendbuf, int len, const struct pseudo_hdr front)
{
    char str[MAXLINE];
    
    bzero(&str, MAXLINE);
    bcopy(&front, str, sizeof(front));
    bcopy(sendbuf, str+sizeof(front), len);
    
    struct pseudo_hdr *ptr;
    ptr = (struct pseudo_hdr *)str;

    return in_chksum((unsigned short *)ptr, 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;
}

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包没有问题。

 hex_dump 输出:

0000 45 00 00 28 27 10 00 00 80 06 00 50 c0 a8 0b 68 
0010 73 ef d3 70 21 98 00 50 05 f5 e1 00 00 00 00 00 
0020 50 02 ff ff 93 94 00 00

使用wireshark验证数据包是否正确,'文件’->"从hex dump 导入(I)..."

“封装类型”选择“Raw IPv4”

导入后wireshark解析的数据包如下:

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

分类: Linux 网络编程笔记

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值