Linux下的raw Socket(原始套接字)编程
2017-06-29 11:21
459 查看
前言
本文所述代码托管在https://github.com/Wuchenwcf/MyCode/blob/master/C%2B%2B/Linux/rawSocketTest.cpp简介
最近项目需要用到rawSocket来进行自定义IP报文的源地址。windows从winxp sp2开始便对raw socket进行了限制。
【1】只能发送UDP包
【2】只能发送正确的UDP包
【3】不能冒充源地址,即,源地址只能填本机地址。
所以raw socket只有在Linux平台上才能发挥它本该有的功能。
注意
网上很多raw Socket的代码很坑,尤其是这个:http://www.tenouk.com/Module43a.html。看起来很正规很牛逼的样子,实际上IP报文都组错了。而且sendto函数调用错误,导致数据报只能在本地环路中收发,根本就不经过网卡,也就发不出去。博主参考了他的代码,结果卡了一天。唉。代码
代码注释写的很清楚,看官自己看吧#include <unistd.h> #include <stdio.h> #include <sys/socket.h> #include <netinet/ip.h> #include <netinet/udp.h> #include<memory.h> #include<stdlib.h> #include <linux/if_ether.h> #include <linux/if_packet.h> // sockaddr_ll #include<arpa/inet.h> #include<netinet/if_ether.h> #include<iomanip> #include<iostream> // The packet length #define PCKT_LEN 100 //UDP的伪头部 struct UDP_PSD_Header { u_int32_t src; u_int32_t des; u_int8_t mbz; u_int8_t ptcl; u_int16_t len; }; //计算校验和 unsigned short csum(unsigned short *buf, int nwords) { unsigned long sum; for (sum = 0; nwords > 0; nwords--) { sum += *buf++; } sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); return (unsigned short)(~sum); } // Source IP, source port, target IP, target port from the command line arguments int main(int argc, char *argv[]) { int sd; char buffer[PCKT_LEN] ; //查询www.chongfer.cn的DNS报文 unsigned char DNS[] = { 0xd8, 0xcb , 0x01, 0x00, 0x00, 0x01, 0x00 ,0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x77, 0x77, 0x77, 0x08, 0x63, 0x68, 0x6f, 0x6e, 0x67, 0x66, 0x65, 0x72, 0x02, 0x63, 0x6e, 0x00, 0x00, 0x01, 0x00, 0x01 }; struct iphdr *ip = (struct iphdr *) buffer; struct udphdr *udp = (struct udphdr *) (buffer + sizeof(struct iphdr)); // Source and destination addresses: IP and port struct sockaddr_in sin, din; int one = 1; const int *val = &one; //缓存清零 memset(buffer, 0, PCKT_LEN); if (argc != 5) { printf("- Invalid parameters!!!\n"); printf("- Usage %s <source hostname/IP> <source port> <target hostname/IP> <target port>\n", argv[0]); exit(-1); } // Create a raw socket with UDP protocol sd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP); if (sd < 0) { perror("socket() error"); // If something wrong just exit exit(-1); } else printf("socket() - Using SOCK_RAW socket and UDP protocol is OK.\n"); //IPPROTO_TP说明用户自己填写IP报文 //IP_HDRINCL表示由内核来计算IP报文的头部校验和,和填充那个IP的id if (setsockopt(sd, IPPROTO_IP, IP_HDRINCL, val, sizeof(int))) { perror("setsockopt() error"); exit(-1); } else printf("setsockopt() is OK.\n"); // The source is redundant, may be used later if needed // The address family sin.sin_family = AF_INET; din.sin_family = AF_INET; // Port numbers sin.sin_port = htons(atoi(argv[2])); din.sin_port = htons(atoi(argv[4])); // IP addresses sin.sin_addr.s_addr = inet_addr(argv[1]); din.sin_addr.s_addr = inet_addr(argv[3]); // Fabricate the IP header or we can use the // standard header structures but assign our own values. ip->ihl = 5; ip->version = 4;//报头长度,4*32=128bit=16B ip->tos = 0; // 服务类型 ip->tot_len = ((sizeof(struct iphdr) + sizeof(struct udphdr)+sizeof(DNS))); //ip->id = htons(54321);//可以不写 ip->ttl = 64; // hops生存周期 ip->protocol = 17; // UDP ip->check = 0; // Source IP address, can use spoofed address here!!! ip->saddr = inet_addr(argv[1]); // The destination IP address ip->daddr = inet_addr(argv[3]); // Fabricate the UDP header. Source port number, redundant udp->source = htons(atoi(argv[2]));//源端口 // Destination port number udp->dest = htons(atoi(argv[4]));//目的端口 udp->len = htons(sizeof(struct udphdr)+sizeof(DNS));//长度 //forUDPCheckSum用来计算UDP报文的校验和用 //UDP校验和需要计算 伪头部、UDP头部和数据部分 char * forUDPCheckSum = new char[sizeof(UDP_PSD_Header) + sizeof(udphdr)+sizeof(DNS)+1]; memset(forUDPCheckSum, 0, sizeof(UDP_PSD_Header) + sizeof(udphdr) + sizeof(DNS) + 1); UDP_PSD_Header * udp_psd_Header = (UDP_PSD_Header *)forUDPCheckSum; udp_psd_Header->src = inet_addr(argv[1]); udp_psd_Header->des = inet_addr(argv[3]); udp_psd_Header->mbz = 0; udp_psd_Header->ptcl = 17; udp_psd_Header->len = htons(sizeof(udphdr)+sizeof(DNS)); memcpy(forUDPCheckSum + sizeof(UDP_PSD_Header), udp, sizeof(udphdr)); memcpy(forUDPCheckSum + sizeof(UDP_PSD_Header) + sizeof(udphdr), DNS, sizeof(DNS)); //ip->check = csum((unsigned short *)ip, sizeof(iphdr)/2);//可以不用算 //计算UDP的校验和,因为报文长度可能为单数,所以计算的时候要补0 udp->check = csum((unsigned short *)forUDPCheckSum,(sizeof(udphdr)+sizeof(UDP_PSD_Header)+sizeof(DNS)+1)/2); setuid(getpid());//如果不是root用户,需要获取权限 // Send loop, send for every 2 second for 2000000 count printf("Trying...\n"); printf("Using raw socket and UDP protocol\n"); printf("Using Source IP: %s port: %u, Target IP: %s port: %u.\n", argv[1], atoi(argv[2]), argv[3], atoi(argv[4])); std::cout << "Ip length:" << ip->tot_len << std::endl; int count; //将DNS报文拷贝进缓存区 memcpy(buffer + sizeof(iphdr) + sizeof(udphdr), DNS, sizeof(DNS)); for (count = 1; count <= 2000000; count++) { if (sendto(sd, buffer, ip->tot_len, 0, (struct sockaddr *)&din, sizeof(din)) < 0) // Verify { perror("sendto() error"); exit(-1); } else { printf("Count #%u - sendto() is OK.\n", count); sleep(2); } } close(sd); return 0; }
结果
在IP为93的机器上冒充那个IP为94的机器向阿里的DNS服务器114.114.114.114上发送DNS查询报文在IP为94的机器上收到阿里DNS服务器传回来的DNS报文
相关文章推荐
- Linux网络编程:原始套接字的魔力【续】
- 原始套接字编程:raw socket
- 用原始套接字编程实现linux中的 ping 命令 ************************
- linux sock_raw原始套接字编程 (转)和Linux下Libpcap源码分析和包过滤机制
- Linux网络编程:原始套接字编程(下)
- Linux网络编程:原始套接字
- Linux网络编程之原始套接字-ping协议实现
- Linux操作系统网络编程--原始套接字 (1)
- Linux网络编程:原始套接字的魔力
- Linux网络编程:原始套接字的魔力【续】
- Linux网络编程--10. 原始套接字 --11. 后记
- Linux网络编程:原始套接字的魔力【续】
- Linux网络编程:10. 原始套接字
- linux下原始套接字编程错误:Operation not supported
- Linux网络编程: 原始套接字
- Linux原始套接字编程
- linux 网络编程--原始套接字(下)
- Linux网络编程:原始套接字的魔力【上】
- linux sock_raw原始套接字编程
- Linux下使用RAW SOCKET原始套接字构造UDP原始数据帧广播到局域网,在局域网的另一台计算机上显示UDP发送的信息