linux原始套接字(3)-构造IP_TCP发送与接收

一.概述                                                   

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

1 sockfd = socket(PF_INET, SOCK_RAW, IPPROTO_TCP);

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

1 int on = 1;
2 setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on));

ip报文格式:

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

 1 struct ip
 2 {
 3 #if __BYTE_ORDER == __LITTLE_ENDIAN
 4     unsigned int ip_hl:4;        /* header length */
 5     unsigned int ip_v:4;        /* version */
 6 #endif
 7 #if __BYTE_ORDER == __BIG_ENDIAN
 8     unsigned int ip_v:4;        /* version */
 9     unsigned int ip_hl:4;        /* header length */
10 #endif
11     u_int8_t ip_tos;            /* type of service */
12     u_short ip_len;            /* total length */
13     u_short ip_id;            /* identification */
14     u_short ip_off;            /* fragment offset field */
15 #define    IP_RF 0x8000            /* reserved fragment flag */
16 #define    IP_DF 0x4000            /* dont fragment flag */
17 #define    IP_MF 0x2000            /* more fragments flag */
18 #define    IP_OFFMASK 0x1fff        /* mask for fragmenting bits */
19     u_int8_t ip_ttl;            /* time to live */
20     u_int8_t ip_p;            /* protocol */
21     u_short ip_sum;            /* checksum */
22     struct in_addr ip_src, ip_dst;    /* source and dest address */
23 };

tcp报文格式:

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

 1 struct tcphdr
 2   {
 3     __extension__ union
 4     {
 5       struct
 6       {
 7     u_int16_t th_sport;        /* source port */
 8     u_int16_t th_dport;        /* destination port */
 9     tcp_seq th_seq;        /* sequence number */
10     tcp_seq th_ack;        /* acknowledgement number */
11 # if __BYTE_ORDER == __LITTLE_ENDIAN
12     u_int8_t th_x2:4;        /* (unused) */
13     u_int8_t th_off:4;        /* data offset */
14 # endif
15 # if __BYTE_ORDER == __BIG_ENDIAN
16     u_int8_t th_off:4;        /* data offset */
17     u_int8_t th_x2:4;        /* (unused) */
18 # endif
19     u_int8_t th_flags;
20 # define TH_FIN    0x01
21 # define TH_SYN    0x02
22 # define TH_RST    0x04
23 # define TH_PUSH    0x08
24 # define TH_ACK    0x10
25 # define TH_URG    0x20
26     u_int16_t th_win;        /* window */
27     u_int16_t th_sum;        /* checksum */
28     u_int16_t th_urp;        /* urgent pointer */
29       };
30       struct
31       {
32     u_int16_t source;
33     u_int16_t dest;
34     u_int32_t seq;
35     u_int32_t ack_seq;
36 # if __BYTE_ORDER == __LITTLE_ENDIAN
37     u_int16_t res1:4;
38     u_int16_t doff:4;
39     u_int16_t fin:1;
40     u_int16_t syn:1;
41     u_int16_t rst:1;
42     u_int16_t psh:1;
43     u_int16_t ack:1;
44     u_int16_t urg:1;
45     u_int16_t res2:2;
46 # elif __BYTE_ORDER == __BIG_ENDIAN
47     u_int16_t doff:4;
48     u_int16_t res1:4;
49     u_int16_t res2:2;
50     u_int16_t urg:1;
51     u_int16_t ack:1;
52     u_int16_t psh:1;
53     u_int16_t rst:1;
54     u_int16_t syn:1;
55     u_int16_t fin:1;
56 # else
57 #  error "Adjust your <bits/endian.h> defines"
58 # endif
59     u_int16_t window;
60     u_int16_t check;
61     u_int16_t urg_ptr;
62       };
63     };
64 };

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

二.构造IP_TCP发送                              

  1 /**
  2  * @file ip_tcp_send.c
  3  */
  4
  5 #include <stdio.h>
  6 #include <stdlib.h>
  7 #include <string.h>
  8 #include <unistd.h>
  9 #include <sys/socket.h>
 10 #include <arpa/inet.h>
 11 #include <netinet/in.h>
 12 #include <netinet/ip.h>
 13 #include <netinet/tcp.h>
 14
 15 /* ip首部长度 */
 16 #define IP_HEADER_LEN sizeof(struct ip)
 17 /* tcp首部长度 */
 18 #define TCP_HEADER_LEN sizeof(struct tcphdr)
 19 /* ip首部 + tcp首部长度 */
 20 #define IP_TCP_HEADER_LEN IP_HEADER_LEN + TCP_HEADER_LEN
 21
 22 void err_exit(const char *err_msg)
 23 {
 24     perror(err_msg);
 25     exit(1);
 26 }
 27
 28 /* 填充ip首部 */
 29 struct ip *fill_ip_header(const char *src_ip, const char *dst_ip, int ip_packet_len)
 30 {
 31     struct ip *ip_header;
 32
 33     ip_header = (struct ip *)malloc(IP_HEADER_LEN);
 34     ip_header->ip_v = IPVERSION;
 35     ip_header->ip_hl = sizeof(struct ip) / 4;        /* 这里注意,ip首部长度是指占多个32位的数量,4字节=32位,所以除以4 */
 36     ip_header->ip_tos = 0;
 37     ip_header->ip_len = htons(ip_packet_len);        /* 整个IP数据报长度,包括普通数据 */
 38     ip_header->ip_id = 0;                            /* 让内核自己填充标识位 */
 39     ip_header->ip_off = 0;
 40     ip_header->ip_ttl = MAXTTL;
 41     ip_header->ip_p = IPPROTO_TCP;                   /* ip包封装的协议类型 */
 42     ip_header->ip_sum = 0;                           /* 让内核自己计算校验和 */
 43     ip_header->ip_src.s_addr = inet_addr(src_ip);    /* 源IP地址 */
 44     ip_header->ip_dst.s_addr = inet_addr(dst_ip);    /* 目标IP地址 */
 45
 46     return ip_header;
 47 }
 48
 49 /* 填充tcp首部 */
 50 struct tcphdr *fill_tcp_header(int src_port, int dst_port)
 51 {
 52     struct tcphdr *tcp_header;
 53
 54     tcp_header = (struct tcphdr *)malloc(TCP_HEADER_LEN);
 55     tcp_header->source = htons(src_port);
 56     tcp_header->dest = htons(dst_port);
 57     /* 同IP首部一样,这里是占32位的字节多少个 */
 58     tcp_header->doff = sizeof(struct tcphdr) / 4;
 59     /* 发起连接 */
 60     tcp_header->syn = 1;
 61     tcp_header->window = 4096;
 62     tcp_header->check = 0;
 63
 64     return tcp_header;
 65 }
 66
 67 /* 发送ip_tcp报文 */
 68 void ip_tcp_send(const char *src_ip, int src_port, const char *dst_ip, int dst_port, const char *data)
 69 {
 70     struct ip *ip_header;
 71     struct tcphdr *tcp_header;
 72     struct sockaddr_in dst_addr;
 73     socklen_t sock_addrlen = sizeof(struct sockaddr_in);
 74
 75     int data_len = strlen(data);
 76     int ip_packet_len = IP_TCP_HEADER_LEN + data_len;
 77     char buf[ip_packet_len];
 78     int sockfd, ret_len, on = 1;
 79
 80     bzero(&dst_addr, sock_addrlen);
 81     dst_addr.sin_family = PF_INET;
 82     dst_addr.sin_addr.s_addr = inet_addr(dst_ip);
 83     dst_addr.sin_port = htons(dst_port);
 84
 85     /* 创建tcp原始套接字 */
 86     if ((sockfd = socket(PF_INET, SOCK_RAW, IPPROTO_TCP)) == -1)
 87         err_exit("socket()");
 88
 89     /* 开启IP_HDRINCL,自定义IP首部 */
 90     if (setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) == -1)
 91         err_exit("setsockopt()");
 92
 93     /* ip首部 */
 94     ip_header = fill_ip_header(src_ip, dst_ip, ip_packet_len);
 95     /* tcp首部 */
 96     tcp_header = fill_tcp_header(src_port, dst_port);
 97
 98     bzero(buf, ip_packet_len);
 99     memcpy(buf, ip_header, IP_HEADER_LEN);
100     memcpy(buf + IP_HEADER_LEN, tcp_header, TCP_HEADER_LEN);
101     memcpy(buf + IP_TCP_HEADER_LEN, data, data_len);
102
103     /* 发送报文 */
104     ret_len = sendto(sockfd, buf, ip_packet_len, 0, (struct sockaddr *)&dst_addr, sock_addrlen);
105     if (ret_len > 0)
106         printf("sendto() ok!!!\n");
107     else printf("sendto() failed\n");
108
109     close(sockfd);
110     free(ip_header);
111     free(tcp_header);
112 }
113
114 int main(int argc, const char *argv[])
115 {
116     if (argc != 6)
117     {
118         printf("usage:%s src_ip src_port dst_ip dst_port data\n", argv[0]);
119         exit(1);
120     }
121
122     /* 发送ip_tcp报文 */
123     ip_tcp_send(argv[1], atoi(argv[2]), argv[3], atoi(argv[4]), argv[5]);
124
125     return 0;
126 }

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

三.接收IP_TCP                                      

 1 /**
 2  * @file ip_tcp_recv.c
 3  */
 4
 5 #include <stdio.h>
 6 #include <stdlib.h>
 7 #include <string.h>
 8 #include <unistd.h>
 9 #include <sys/socket.h>
10 #include <arpa/inet.h>
11 #include <netinet/in.h>
12 #include <netinet/ip.h>
13 #include <netinet/tcp.h>
14
15 /* ip首部长度 */
16 #define IP_HEADER_LEN sizeof(struct ip)
17 /* tcp首部长度 */
18 #define TCP_HEADER_LEN sizeof(struct tcphdr)
19 /* ip首部 + tcp首部长度 */
20 #define IP_TCP_HEADER_LEN IP_HEADER_LEN + TCP_HEADER_LEN
21 /* 接收数据缓冲大小 */
22 #define BUFFER_SIZE 1024
23 /* ip首部 + tcp首部 + 数据缓冲区大小 */
24 #define IP_TCP_BUFF_SIZE IP_TCP_HEADER_LEN + BUFFER_SIZE
25
26 void err_exit(const char *err_msg)
27 {
28     perror(err_msg);
29     exit(1);
30 }
31
32 /* 原始套接字接收 */
33 void raw_socket_recv()
34 {
35     struct ip *ip_header;
36     struct tcphdr *tcp_header;
37     int sock_raw_fd, ret_len;
38     char buf[IP_TCP_BUFF_SIZE];
39
40     if ((sock_raw_fd = socket(PF_INET, SOCK_RAW, IPPROTO_TCP)) == -1)
41         err_exit("socket()");
42
43     /* 接收数据 */
44     while (1)
45     {
46         bzero(buf, IP_TCP_BUFF_SIZE);
47         ret_len = recv(sock_raw_fd, buf, IP_TCP_BUFF_SIZE, 0);
48         if (ret_len > 0)
49         {
50             /* 取出ip首部 */
51             ip_header = (struct ip *)buf;
52             /* 取出tcp首部 */
53             tcp_header = (struct tcphdr *)(buf + IP_HEADER_LEN);
54             printf("=======================================\n");
55             printf("from ip:%s\n", inet_ntoa(ip_header->ip_src));
56             printf("from port:%d\n", ntohs(tcp_header->source));
57             /* 取出数据 */
58             printf("get data:%s\n", buf + IP_TCP_HEADER_LEN);
59         }
60     }
61
62     close(sock_raw_fd);
63 }
64
65 int main(void)
66 {
67     /* 原始套接字接收 */
68     raw_socket_recv();
69
70     return 0;
71 }

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

四.实验                                                  

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

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

wireshark结果:

时间: 2024-10-10 20:28:46

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

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

一.概述                                                    同上一篇tcp一样,udp也是封装在ip报文里面.创建UDP的原始套接字如下: 1 (sockfd = socket(PF_INET, SOCK_RAW, IPPROTO_UDP); 同样,如果要构造udp的ip首部,要开启IP_HDRINCL选项! udp首部格式: udp的不可靠性,比tcp报文简单很多.上面的16位UDP长度是UDP首部+普通数据的总长度,这点跟ip首部的16位总

Linux 原始套接字--myping的实现

一.套接字的类型 A.流套接字(SOCK_STREAM) 用于提供面向连接.可靠的数据传输服务,其使用传输层的TCP协议 B.数据报套接字(SOCK_DGRAM) 用于提供一个无连接.不可靠的服务,其使用传输层上的UDP协议 C.原始套接字(SOCK_RAM) 原始套接字是相对表中套接字(即前面两种套接字)而言的.它与标准套接字的区别是原始套接字可以读写内核没有处理的IP数据包,流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据. 所以要访问其他协议的数据必须使用原始套接字.

关于linux 原始套接字编程

关于linux 网络编程最权威的书是<<unix网络编程>>,但是看这本书时有些内容你可能理解的不是很深刻,或者说只知其然而不知其所以然,那么如果你想搞懂的话那么我建议你可以看看网络协议栈的实现. 函数原型是 int socket(int domain, int type, int protocol); 其中domain 中AF_INET , AF_UNIT 较为常用,分别创建inet 域套接字和unix域套接字,unix套接字与文件相关.平时80%用的套接字都是AF_INET.这

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

一.概述                                                    上一篇arp请求使用的是链路层的原始套接字.icmp封装在ip数据报里面,所以icmp请求可以直接使用网络层的原始套接字,即socket()第一个参数是PF_INET.如下: 1 sockfd = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP); icmp报文不同的类型有不同的格式,我们以icmp回显请求和会显应答报文格式(即ping程序使用的报文类型)

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

一.概述                                                   以太网的arp数据包结构: arp结构op操作参数:1为请求,2为应答. 常用的数据结构如下: 1.物理地址结构位于netpacket/packet.h 1 struct sockaddr_ll 2 { 3 unsigned short int sll_family; 4 unsigned short int sll_protocol; 5 int sll_ifindex; 6 unsi

Linux Socket 原始套接字编程

对于linux网络编程来说,可以简单的分为标准套接字编程和原始套接字编程,标准套接字主要就是应用层数据的传输,原始套接字则是可以获得不止是应用层的其他层不同协议的数据.与标准套接字相区别的主要是要开发之自己构建协议头.对于原始套接字编程有些细节性的东西还是需要注意的. 1. 原始套接字创建 原始套接字的编程和udp网络编程的流程有点类似,但是原始套接字编程中不需要bind操作,因为在数据接收和发送过程中使用sendto和recvfrom函数实现数据的接收和发送.不过不是说原始套接字不能使用bin

Linux网络编程:原始套接字的魔力【上】

基于原始套接字编程 在开发面向连接的TCP和面向无连接的UDP程序时,我们所关心的核心问题在于数据收发层面,数据的传输特性由TCP或UDP来保证: 也就是说,对于TCP或UDP的程序开发,焦点在Data字段,我们没法直接对TCP或UDP头部字段进行赤裸裸的修改,当然还有IP头.换句话说,我们对它们头部操作的空间非常受限,只能使用它们已经开放给我们的诸如源.目的IP,源.目的端口等等. 今天我们讨论一下原始套接字的程序开发,用它作为入门协议栈的进阶跳板太合适不过了.OK闲话不多说,进入正题. 原始

Linux基础(11)原始套接字

一边接收函数返回一边判断返回值时一定要把接收的优先级加()提高再去判断 例 if((sockfd = socket()) < 0) 问题: 如何实现SYN扫描器扫描端口 , 比如AB两个设备要进行连接 , A通过端口发一个SYN包给B,B在收到后返回一个ACK包确认连接 , 但是在不确定B端口号时 该如何进行连接 , 答: A给B的每一个端都发一个SYN包, 如果哪个有返回说明端口是开放的, TCP和UDP都无法发实现这样的连接方式 , 所以要使用原始套接字 #include <netinet

Linux网络编程——原始套接字实例:简单版网络数据分析器

通过<Linux网络编程--原始套接字编程>得知,我们可以通过原始套接字以及 recvfrom( ) 可以获取链路层的数据包,那我们接收的链路层数据包到底长什么样的呢? 链路层封包格式 MAC 头部(有线局域网) 注意:CRC.PAD 在组包时可以忽略 链路层数据包的其中一种情况: unsigned char msg[1024] = { //--------------组MAC--------14------ 0xb8, 0x88, 0xe3, 0xe1, 0x10, 0xe6, // dst