一.概述                                                   

tcp报文封装在ip报文中,创建tcp的原始套接字如下:

 sockfd = socket(PF_INET, SOCK_RAW, IPPROTO_TCP);

此时只能构造tcp报文,如果想进一步构造ip首部,那么就要开启sockfd的IP_HDRINCL选项:

 ;
 setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on));

ip报文格式:

ip首部结构定义在netinet/ip.h

 struct ip
 {
 #if __BYTE_ORDER == __LITTLE_ENDIAN
     unsigned ;        /* header length */
     unsigned ;        /* version */
 #endif
 #if __BYTE_ORDER == __BIG_ENDIAN
     unsigned ;        /* version */
     unsigned ;        /* header length */
 #endif
     u_int8_t ip_tos;            /* type of service */
     u_short ip_len;            /* total length */
     u_short ip_id;            /* identification */
     u_short ip_off;            /* fragment offset field */
 #define    IP_RF 0x8000            /* reserved fragment flag */
 #define    IP_DF 0x4000            /* dont fragment flag */
 #define    IP_MF 0x2000            /* more fragments flag */
 #define    IP_OFFMASK 0x1fff        /* mask for fragmenting bits */
     u_int8_t ip_ttl;            /* time to live */
     u_int8_t ip_p;            /* protocol */
     u_short ip_sum;            /* checksum */
     struct in_addr ip_src, ip_dst;    /* source and dest address */
 };

tcp报文格式:

tcp首部结构定义在netinet/tcp.h:

 struct tcphdr
   {
     __extension__ union
     {
       struct
       {
     u_int16_t th_sport;        /* source port */
     u_int16_t th_dport;        /* destination port */
     tcp_seq th_seq;        /* sequence number */
     tcp_seq th_ack;        /* acknowledgement number */
 # if __BYTE_ORDER == __LITTLE_ENDIAN
     u_int8_t th_x2:;        /* (unused) */
     u_int8_t th_off:;        /* data offset */
 # endif
 # if __BYTE_ORDER == __BIG_ENDIAN
     u_int8_t th_off:;        /* data offset */
     u_int8_t th_x2:;        /* (unused) */
 # endif
     u_int8_t th_flags;
 # define TH_FIN    0x01
 # define TH_SYN    0x02
 # define TH_RST    0x04
 # define TH_PUSH    0x08
 # define TH_ACK    0x10
 # define TH_URG    0x20
     u_int16_t th_win;        /* window */
     u_int16_t th_sum;        /* checksum */
     u_int16_t th_urp;        /* urgent pointer */
       };
       struct
       {
     u_int16_t source;
     u_int16_t dest;
     u_int32_t seq;
     u_int32_t ack_seq;
 # if __BYTE_ORDER == __LITTLE_ENDIAN
     u_int16_t res1:;
     u_int16_t doff:;
     u_int16_t fin:;
     u_int16_t syn:;
     u_int16_t rst:;
     u_int16_t psh:;
     u_int16_t ack:;
     u_int16_t 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;
     u_int16_t check;
     u_int16_t urg_ptr;
       };
     };
 };

对照结构的定义和上面结构图很容易理解。注意:ip和tcp的四位首部长度都是指占多少个32bit。如果普通ip首部长度是20字节,4字节占32位,那么这个值就是20/4=5。

二.构造IP_TCP发送                              

 /**
  * @file ip_tcp_send.c
  */

 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 #include <sys/socket.h>
 #include <arpa/inet.h>
 #include <netinet/in.h>
 #include <netinet/ip.h>
 #include <netinet/tcp.h>

 /* ip首部长度 */
 #define IP_HEADER_LEN sizeof(struct ip)
 /* tcp首部长度 */
 #define TCP_HEADER_LEN sizeof(struct tcphdr)
 /* ip首部 + tcp首部长度 */
 #define IP_TCP_HEADER_LEN IP_HEADER_LEN + TCP_HEADER_LEN

 void err_exit(const char *err_msg)
 {
     perror(err_msg);
     exit();
 }

 /* 填充ip首部 */
 struct ip *fill_ip_header(const char *src_ip, const char *dst_ip, int ip_packet_len)
 {
     struct ip *ip_header;

     ip_header = (struct ip *)malloc(IP_HEADER_LEN);
     ip_header->ip_v = IPVERSION;
     ip_header->ip_hl = ;        /* 这里注意,ip首部长度是指占多个32位的数量,4字节=32位,所以除以4 */
     ip_header->ip_tos = ;
     ip_header->ip_len = htons(ip_packet_len);        /* 整个IP数据报长度,包括普通数据 */
     ip_header->ip_id = ;                            /* 让内核自己填充标识位 */
     ip_header->ip_off = ;
     ip_header->ip_ttl = MAXTTL;
     ip_header->ip_p = IPPROTO_TCP;                   /* ip包封装的协议类型 */
     ip_header->ip_sum = ;                           /* 让内核自己计算校验和 */
     ip_header->ip_src.s_addr = inet_addr(src_ip);    /* 源IP地址 */
     ip_header->ip_dst.s_addr = inet_addr(dst_ip);    /* 目标IP地址 */

     return ip_header;
 }

 /* 填充tcp首部 */
 struct tcphdr *fill_tcp_header(int src_port, int dst_port)
 {
     struct tcphdr *tcp_header;

     tcp_header = (struct tcphdr *)malloc(TCP_HEADER_LEN);
     tcp_header->source = htons(src_port);
     tcp_header->dest = htons(dst_port);
     /* 同IP首部一样,这里是占32位的字节多少个 */
     tcp_header->doff = ;
     /* 发起连接 */
     tcp_header->syn = ;
     tcp_header->window = ;
     tcp_header->check = ;

     return tcp_header;
 }

 /* 发送ip_tcp报文 */
 void ip_tcp_send(const char *src_ip, int src_port, const char *dst_ip, int dst_port, const char *data)
 {
     struct ip *ip_header;
     struct tcphdr *tcp_header;
     struct sockaddr_in dst_addr;
     socklen_t sock_addrlen = sizeof(struct sockaddr_in);

     int data_len = strlen(data);
     int ip_packet_len = IP_TCP_HEADER_LEN + data_len;
     char buf[ip_packet_len];
     ;

     bzero(&dst_addr, sock_addrlen);
     dst_addr.sin_family = PF_INET;
     dst_addr.sin_addr.s_addr = inet_addr(dst_ip);
     dst_addr.sin_port = htons(dst_port);

     /* 创建tcp原始套接字 */
     )
         err_exit("socket()");

     /* 开启IP_HDRINCL,自定义IP首部 */
     )
         err_exit("setsockopt()");

     /* ip首部 */
     ip_header = fill_ip_header(src_ip, dst_ip, ip_packet_len);
     /* tcp首部 */
     tcp_header = fill_tcp_header(src_port, dst_port);

     bzero(buf, ip_packet_len);
     memcpy(buf, ip_header, IP_HEADER_LEN);
     memcpy(buf + IP_HEADER_LEN, tcp_header, TCP_HEADER_LEN);
     memcpy(buf + IP_TCP_HEADER_LEN, data, data_len);

     /* 发送报文 */
     ret_len = sendto(sockfd, buf, ip_packet_len, , (struct sockaddr *)&dst_addr, sock_addrlen);
     )
         printf("sendto() ok!!!\n");
     else printf("sendto() failed\n");

     close(sockfd);
     free(ip_header);
     free(tcp_header);
 }

 int main(int argc, const char *argv[])
 {
     )
     {
         printf(]);
         exit();
     }

     /* 发送ip_tcp报文 */
     ip_tcp_send(argv[], atoi(argv[]), argv[], atoi(argv[]), argv[]);

     ;
 }

流程:命令行接收的参数分别是:源ip地址,源端口,目标ip地址,目标端口,普通数据。然后通过源/目标ip构造ip首部,通过源/目标端口构造tcp首部。把目标ip/端口填充到sockaddr_in,最后把构造的ip首部,tcp首部,普通数据全部复制到缓冲区,一并发送到刚刚的sockaddr_in的地址!!!

三.接收IP_TCP                                      

 /**
  * @file ip_tcp_recv.c
  */

 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 #include <sys/socket.h>
 #include <arpa/inet.h>
 #include <netinet/in.h>
 #include <netinet/ip.h>
 #include <netinet/tcp.h>

 /* ip首部长度 */
 #define IP_HEADER_LEN sizeof(struct ip)
 /* tcp首部长度 */
 #define TCP_HEADER_LEN sizeof(struct tcphdr)
 /* ip首部 + tcp首部长度 */
 #define IP_TCP_HEADER_LEN IP_HEADER_LEN + TCP_HEADER_LEN
 /* 接收数据缓冲大小 */
 #define BUFFER_SIZE 1024
 /* ip首部 + tcp首部 + 数据缓冲区大小 */
 #define IP_TCP_BUFF_SIZE IP_TCP_HEADER_LEN + BUFFER_SIZE

 void err_exit(const char *err_msg)
 {
     perror(err_msg);
     exit();
 }

 /* 原始套接字接收 */
 void raw_socket_recv()
 {
     struct ip *ip_header;
     struct tcphdr *tcp_header;
     int sock_raw_fd, ret_len;
     char buf[IP_TCP_BUFF_SIZE];

     )
         err_exit("socket()");

     /* 接收数据 */
     )
     {
         bzero(buf, IP_TCP_BUFF_SIZE);
         ret_len = recv(sock_raw_fd, buf, IP_TCP_BUFF_SIZE, );
         )
         {
             /* 取出ip首部 */
             ip_header = (struct ip *)buf;
             /* 取出tcp首部 */
             tcp_header = (struct tcphdr *)(buf + IP_HEADER_LEN);
             printf("=======================================\n");
             printf("from ip:%s\n", inet_ntoa(ip_header->ip_src));
             printf("from port:%d\n", ntohs(tcp_header->source));
             /* 取出数据 */
             printf("get data:%s\n", buf + IP_TCP_HEADER_LEN);
         }
     }

     close(sock_raw_fd);
 }

 int main(void)
 {
     /* 原始套接字接收 */
     raw_socket_recv();

     ;
 }

流程:创建TCP类型的原始套接,原始套接字是点对点传输,不像TCP/UDP是端对端,故原始套接字不存在端口概念,可以直接接收。这里接收的是整个IP报文,里面包含了TCP报文。接收后依次取出ip首部,tcp首部,普通数据!!!

四.实验                                                  

发送端和接收端都在本机,我们打开wireshark监听lo接口。以root身份运行2个程序:

上面发送了2个tcp包,每次的源ip,源端口都是伪造的。DDOS程序就是这个原理。

wireshark结果:

linux原始套接字(3)-构造IP_TCP发送与接收的更多相关文章

  1. linux原始套接字(4)-构造IP_UDP

    一.概述                                                    同上一篇tcp一样,udp也是封装在ip报文里面.创建UDP的原始套接字如下: (soc ...

  2. Linux原始套接字实现分析---转

    http://blog.chinaunix.net/uid-27074062-id-3388166.html 本文从IPV4协议栈原始套接字的分类入手,详细介绍了链路层和网络层原始套接字的特点及其内核 ...

  3. Linux原始套接字抓取底层报文

    1.原始套接字使用场景 我们平常所用到的网络编程都是在应用层收发数据,每个程序只能收到发给自己的数据,即每个程序只能收到来自该程序绑定的端口的数据.收到的数据往往只包括应用层数据,原有的头部信息在传递 ...

  4. 关于linux 原始套接字编程

    关于linux 网络编程最权威的书是<<unix网络编程>>,但是看这本书时有些内容你可能理解的不是很深刻,或者说只知其然而不知其所以然,那么如果你想搞懂的话那么我建议你可以看 ...

  5. linux原始套接字(2)-icmp请求与接收

    一.概述                                                    上一篇arp请求使用的是链路层的原始套接字.icmp封装在ip数据报里面,所以icmp请 ...

  6. linux原始套接字(1)-arp请求与接收

    一.概述                                                   以太网的arp数据包结构: arp结构op操作参数:1为请求,2为应答. 常用的数据结构如 ...

  7. Linux Socket 原始套接字编程

    对于linux网络编程来说,可以简单的分为标准套接字编程和原始套接字编程,标准套接字主要就是应用层数据的传输,原始套接字则是可以获得不止是应用层的其他层不同协议的数据.与标准套接字相区别的主要是要开发 ...

  8. UNP——原始套接字

    1.原始套接字的用处 使用原始套接字可以构造或读取网际层及其以上报文. 具体来说,可以构造 ICMP, IGMP 协议报文,通过开启 IP_HDRINCL 套接字选项,进而自定义 IPv4首部. 2. ...

  9. Linux基础(11)原始套接字

    一边接收函数返回一边判断返回值时一定要把接收的优先级加()提高再去判断 例 if((sockfd = socket()) < 0) 问题: 如何实现SYN扫描器扫描端口 , 比如AB两个设备要进 ...

随机推荐

  1. get传递中文产生乱码的解决方式汇总

    1 最基本的乱码问题. 这个乱码问题是最简单的乱码问题.一般新会出现.就是页面编码不一致导致的乱码. <%@ page language="java" pageEncodin ...

  2. 高效 Java Web 开发框架 JessMA v3.3.1 正式发布

    JessMA(原名:Portal-Basic)是一套功能完备的高性能 Full-Stack Web 应用开发框架,内置可扩展的 MVC Web 基础架构和 DAO 数据库访问组件(内部已提供了 Hib ...

  3. Hibernate(八)__级联操作、struts+hibernate+接口编程架构

    级联操作 所谓级联操作就是说,当你进行主对象某个操作时,从对象hibernate自动完成相应操作. 比如: Department <---->Student 对象关系,我希望当我删除一个d ...

  4. Lua-泛型for循环 pairs和ipairs的区别

    先看一段简单的代码: local mytable = { , , aa = "abc", subtable = {}, , } --for循环1 print("for - ...

  5. MODIS批量处理软件MRT的安装说明

    最近在处理遥感影像的时候遇见了MODIS影像数据,从中MOD13中提取NDVI是相当的重要.在一番的百度之中找到了处理modis影像的神器------MRT 接下来我来说明一下MRT的具体安装,如果之 ...

  6. Wifite.py 修正版脚本代码

    Kali2.0系统自带的WiFite脚本代码中有几行错误,以下是修正后的代码: #!/usr/bin/python # -*- coding: utf-8 -*- """ ...

  7. [Android]AndroidInject增加sqlite3数据库映射注解(ORM)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/3623050.html AndroidInject项目是我写的一 ...

  8. git 设置 key 到服务器,同步代码不需要输入用户名和密码

    1  ssh-keygen -t rsa 2  vim ~/.ssh/id_rsa.pub 3. 添加到git 服务器,这样同步代码就不需要输入密码

  9. Android线程优先级设置方法技巧

    对于Android平台上的线程优先级设置来说可以处理很多并发线程的阻塞问题, 比如很多无关紧要的线程会占用大量的CPU时间,虽然通过了MultiThread来解决慢速I/O但是合理分配优先级对于并发编 ...

  10. Android 常用数据适配器ArrayAdapter

    接着上篇文章<Android 采用Layout Inflater创建一个View对象>,本文采用常用数据适配器ArrayAdapter 新建项目后,在layout文件夹下新建list_it ...