原始套接字

计算机网络中, 原始套接字(raw socket)是一种网络套接字,允许直接发送/接收IP协议数据包而不需要任何传输层协议格式。

简介[编辑]

对于标准的套接字,通常数据按照选定的传输层协议(例如TCPUDP)自动封装,socket用户并不知道在网络介质上广播的数据包含了这种协议包头。

从原始套接字读取数据包含了传输层协议包头。用原始套接字发送数据,是否自动增加传输层协议包头是可选的。

原始套接字用于安全相关的应用程序,如nmap。原始套接字一种可能的用例是在用户空间实现新的传输层协议。[1] 原始套接字常在网络设备上用于路由协议,例如IGMPv4、开放式最短路径优先协议 (OSPF)、互联网控制消息协议 (ICMP)。Ping就是发送一个ICMP响应请求包然后接收ICMP响应回复.[2]

实现[编辑]

大部分套接字API都支持原始套接字功能。Winsock自2001年起在Windows XP上支持原始套接字。但由于安全原因,2004年微软限制了Winsock的原始套接字功能。[3]

例子[编辑]

下例在Linux上实现了Ping程序的主要功能:

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/ip_icmp.h> #include <arpa/inet.h> #include <netdb.h>  /* 校验和計算 */ u_int16_t checksum(unsigned short *buf, int size) { 	unsigned long sum = 0; 	while (size > 1) { 		sum += *buf; 		buf++; 		size -= 2; 	} 	if (size == 1) 		sum += *(unsigned char *)buf; 	sum = (sum & 0xffff) + (sum >> 16); 	sum = (sum & 0xffff) + (sum >> 16); 	return ~sum; }  /* protocol指定的raw socket创建 */ int make_raw_socket(int protocol) { 	int s = socket(AF_INET, SOCK_RAW, protocol); 	if (s < 0) { 		perror("socket"); 		exit(EXIT_FAILURE); 	} 	return s; }  /* ICMP头部的作成 */ void setup_icmphdr(u_int8_t type, u_int8_t code, u_int16_t id, u_int16_t seq, struct icmphdr *icmphdr) { 	memset(icmphdr, 0, sizeof(struct icmphdr)); 	icmphdr->type = type; 	icmphdr->code = code; 	icmphdr->checksum = 0; 	icmphdr->un.echo.id = id; 	icmphdr->un.echo.sequence = seq; 	icmphdr->checksum = checksum((unsigned short *)icmphdr, sizeof(struct icmphdr)); }  int main(int argc, char **argv) { 	int n, soc; 	char buf[1500]; 	struct sockaddr_in addr; 	struct in_addr insaddr; 	struct icmphdr icmphdr; 	struct iphdr *recv_iphdr; 	struct icmphdr *recv_icmphdr;  	if (argc < 2) { 		printf("Usage : %s IP_ADDRESS\n", argv[0]); 		return 1; 	}  	addr.sin_family = AF_INET; 	addr.sin_addr.s_addr = inet_addr(argv[1]); 	soc = make_raw_socket(IPPROTO_ICMP); 	setup_icmphdr(ICMP_ECHO, 0, 0, 0, &icmphdr);  	/* ICMP包的送信 */ 	n = sendto(soc, (char *)&icmphdr, sizeof(icmphdr), 0, (struct sockaddr *)&addr, sizeof(addr)); 	if (n < 1) { 		perror("sendto"); 		return 1; 	}  	/* ICMP包的受信 */ 	n = recv(soc, buf, sizeof(buf), 0); 	if (n < 1) { 		perror("recv"); 		return 1; 	}  	recv_iphdr = (struct iphdr *)buf; 	/* 从IP包头获取IP包的长度,从而确定icmp包头的开始位置 */ 	recv_icmphdr = (struct icmphdr *)(buf + (recv_iphdr->ihl << 2)); 	insaddr.s_addr = recv_iphdr->saddr; 	/* 检查送信包的源地址匹配受信包的目的地址  */ 	if (!strcmp(argv[1], inet_ntoa(insaddr)) && recv_icmphdr->type == ICMP_ECHOREPLY) 		printf("icmp echo reply from %s\n", argv[1]); 	close(soc); 	return 0; } 

下例是binding一个原始套接字并使用:

#include<stdio.h> #include<stdlib.h> #include<sys/socket.h> #include<features.h> #include<linux/if_packet.h> #include<linux/if_ether.h> #include<errno.h> #include<sys/ioctl.h> #include<net/if.h>   #define PACKET_LENGTH	1024   int CreateRawSocket(int protocol_to_sniff) { 	int rawsock; 	if((rawsock = socket(PF_PACKET, SOCK_RAW, htons(protocol_to_sniff)))== -1) { 		perror("Error creating raw socket: "); 		exit(-1); 	} 	return rawsock; }   int BindRawSocketToInterface(char *device, int rawsock, int protocol) { 	struct sockaddr_ll sll; 	struct ifreq ifr;  	memset(&sll, 0, sizeof(sll)); 	memset(&ifr, 0, sizeof(ifr)); 	/* First Get the Interface Index  */ 	strncpy((char *)ifr.ifr_name, device, IFNAMSIZ); 	if((ioctl(rawsock, SIOCGIFINDEX, &ifr)) == -1) { 		printf("Error getting Interface index !\n"); 		exit(-1); 	} 	/* Bind our raw socket to this interface */ 	sll.sll_family = AF_PACKET; 	sll.sll_ifindex = ifr.ifr_ifindex; 	sll.sll_protocol = htons(protocol); 	if((bind(rawsock, (struct sockaddr *)&sll, sizeof(sll)))== -1) { 		perror("Error binding raw socket to interface\n"); 		exit(-1); 	} 	return 1; }   int SendRawPacket(int rawsock, unsigned char *pkt, int pkt_len) { 	int sent= 0;  	/* A simple write on the socket ..thats all it takes ! */  	if((sent = write(rawsock, pkt, pkt_len)) != pkt_len) { 		return 0; 	} 	return 1; }  /* argv[1] is the device e.g. eth0    argv[2] is the number of packets to send */   main(int argc, char **argv) { 	int raw; 	unsigned char packet[PACKET_LENGTH]; 	int num_of_pkts;  	/* Set the packet to all A's */ 	memset(packet, 'A', PACKET_LENGTH); 	/* Create the raw socket */ 	raw = CreateRawSocket(ETH_P_ALL); 	/* Bind raw socket to interface */ 	BindRawSocketToInterface(argv[1], raw, ETH_P_ALL); 	num_of_pkts = atoi(argv[2]); 	while((num_of_pkts--)>0) {  		if(!SendRawPacket(raw, packet, PACKET_LENGTH)) { 			perror("Error sending packet"); 		} else { 			printf("Packet sent successfully\n"); 		} 	} 	close(raw); 	return 0; } 

参见[编辑]

参考文献[编辑]

  1. ^ raw(7): IPv4 raw sockets - Linux man page. die.net. [2017-03-14]. (原始内容存档于2016-09-07). 
  2. ^ Raw IP Networking FAQ. faqs.org. [2017-03-14]. (原始内容存档于2012-01-19). 
  3. ^ Ian Griffiths for IanG on Tap. 12 August, 2004. Raw Sockets Gone in XP SP2页面存档备份,存于互联网档案馆

外部链接[编辑]