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

简介
--

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

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

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

实现
--

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

例子
--

下例在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;
}

```

参见
--

* IPv4包格式
* IPv6包格式

参考文献
----

1. ↑ . die.net.  [2017-03-14]. （原始内容存档于2016-09-07）.
2. ↑ . faqs.org.  [2017-03-14]. （原始内容存档于2012-01-19）.
3. ↑  
Ian Griffiths for IanG on Tap. 12 August, 2004. Raw Sockets Gone in XP SP2 （页面存档备份，存于）

外部链接
----

* 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.