原始套接字

在電腦網絡中, 原始套接字（raw socket）是一種網路套接字，允許直接發送/接收IP協議數據包而不需要任何傳輸層協議格式。

## 簡介

對於標準的套接字，通常數據按照選定的傳輸層協議(例如TCP、UDP)自動封裝，socket使用者並不知道在網路介質上廣播的數據包含了這種協議包頭。

從原始套接字讀取數據包含了傳輸層協議包頭。用原始套接字發送數據，是否自動增加傳輸層協議包頭是可選的。

原始套接字用於安全相關的應用程式，如nmap。原始套接字一種可能的用例是在使用者空間實現新的傳輸層協議。原始套接字常在網路設備上用於路由協議，例如IGMPv4、開放式最短路徑優先協議 (OSPF)、網際網路控制消息協議 (ICMP)。Ping就是發送一個ICMP響應請求包然後接收ICMP響應回復.

## 實現

大部分套接字API都支持原始套接字功能。Winsock自2001年起在Windows XP上支持原始套接字。但由於安全原因，2004年微軟限制了Winsock的原始套接字功能。

Ian Griffiths for IanG on Tap. 12 August, 2004. Raw Sockets Gone in XP SP2

## 例子

下例在Linux上實現了Ping程序的主要功能：

```cpp
#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一個原始套接字並使用：

```cpp
#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;
}
```

## 外部連結

- Net::RawIP; module for Perl applications. Created by Sergey Kolychev.
- Network Programming for Microsoft Windows (ISBN 0-7356-1579-9)
- A little more info on raw sockets and Windows XP SP2 - Michael Howard's Web Log an indication of what's actually allowed on Windows.
- SOCK_RAW Demystified: article describing inner workings of Raw Sockets
- C language examples of Linux raw sockets for IPv4 and IPv6 - David Buchan's C language examples of IPv4 and IPv6 raw sockets for Linux.