linux原始套接字-构造IP_TCP发送与接收
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux原始套接字-构造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结果:
以上是关于linux原始套接字-构造IP_TCP发送与接收的主要内容,如果未能解决你的问题,请参考以下文章