raw socket编程

从应用开发的角度看,SOCK_STREAM、SOCK_DGRAM 这两类套接字似乎已经足够了。因为基于 TCP/IP 的应用,在传输层的确只可能建立于 TCP 或 UDP 协议之上,而这两种套接字SOCK_STREAM、SOCK_DGRAM 又分别对应于 TCP 和 UDP,所以几乎所有所有的应用都可以使用这两种套接字来实现。

但是,从另外的角度,这两种套接字有一些局限:

  • 怎样发送一个 ICMP 协议包?
  • 怎样伪装本地的 IP 地址?
  • 怎样实现一个新设计的协议的数据包?

这两种套接字的局限在于它们只能处理数据载荷,数据包的头部在到达用户程序的时候已经被移除了。

raw socket编程_第1张图片

所以,这里我们要引入一个新的socket类型,原始套接字(SOCK_RAW)。原始套接字应用也很广泛,可以实现sniffer【之前使用pcap实现的sniffer也可以使用raw_socket实现】、IP 欺骗等,基于此,可以实现各种攻击。原始套接字之所以能够做到这一点,是因为它可以绕过系统内核的协议栈,使得用户可以自行构造数据包。原始套接字用于接收和发送原始数据包。 这意味着在以太网层接收的数据包将直接传递到原始套接字。 准确地说,原始套接字绕过正常的TCP / IP处理并将数据包发送到特定的用户应用程序。使用 raw套接字可以实现上至应用层的数据操作,也可以实现下至链路层的数据操作。因此也要求比应用开发更高的权限。

raw socket编程_第2张图片


创建raw socket

要创建套接字,必须知道套接字族、套接字类型和协议三个方面。 对于原始套接字,套接字族可以是AF_INET、PF_INET、AF_PACKET和PF_PACKET;套接字类型是SOCK_RAW;至于协议,可以查阅if_ether.h头文件。 因此,可以看到使用各种参数的组合来创建原始套接字。这也是学习raw socket的时候很容易混淆的一点。

关于AF和PF的区别,上节中已经介绍了。这里,先分析下_NET和_PACKET的区别。

使用man packet,可以查到如下的内容。

使用AF_INET,用户程序无法获得链路层数据,也即,以太网头部。简单来说,使用AF_INET,是面向IP层的原始套接字;使用AF_PACKET,是面向链路层的套接字。

譬如,为了实现sniffer,可以使用下面的代码。为了接收所有分组,可以使用ETH_P_ALL,为了接收IP分组,可以使用ETH_P_IP。

sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))

使用AF_INET时,我们所构造的报文从IP首部之后的第一个字节开始,IP首部由内核自己维护,首部中的协议字段会被设置为我们调用socket()函数时传递给它的protocol字段。也即,如果没有开启IP_HDRINCL选项,那么内核会帮忙处理IP头部。如果设置了IP_HDRINCL选项,那么用户需要自己生成IP头部的数据,其中IP首部中的标识字段和校验和字段总是内核自己维护。可以通过下面代码开启IP_HDRINCL:

const int on = 1;
if(setsockopt(fd,SOL_IP,IP_HDRINCL,&on,sizeof(int)) < 0)
{
  printf("set socket option error!\n");
}

总结一下,对于socket的第一个参数,因为AF_INET、PF_INET、AF_PACKET和PF_PACKET中AF和PF基本等价,所以只需要区分_NET和_PACKET就行;第二个参数是一般是sock_raw;关于第三个参数,取决于第一个参数。

如果第一个参数是AF_INET,那么是面向IP层的套接字,protocol字段定义在netinet/in.h中,常见的包括IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMP和IPPROTO_RAW。前三个参数顾名思义,最后一个IPPROTO_RAW的含义需要额外说明一下:

使用原始套接字编写底层网络程序时,最重要的决策之一是应用程序是否与传输级协议头,IP头一起构建。现在,告诉IP层不要预先添加自己的头的明显方法是调用setsockopt系统调用并设置IP_HDRINCL(包含头)选项。但是,并不总是存在此选项。在Net/3之前的版本中,没有IP_HDRINCL选项,并且没有内核预先添加自己的头的唯一方法是使用特定的内核补丁并将协议设置为IPPROTO_RAW。这些补丁最初是为4.3BSD和Net / 1制作的,以支持需要编写自己的完整IP数据报的Traceroute,因为它需要修改TTL字段。有趣的是,自从IP_HDRINCL出现以来,Linux和FreeBSD选择了不同的方式来继续“传统”。在Linux上将协议设置为PPROTO_RAW时,默认情况下,内核会设置IP_HDRINCL选项,因此不会在其前面加上自己的IP头。

如果第一个参数是AF_PACKET,那么是面向链路层的套接字,第三个参数可以是

  • ETH_P_IP - 只接收目的mac是本机的IP类型数据帧
  • ETH_P_ARP - 只接收目的mac是本机的ARP类型数据帧
  • ETH_P_RARP - 只接收目的mac是本机的RARP类型数据帧
  • ETH_P_PAE - 只接收目的mac是本机的802.1x类型的数据帧
  • ETH_P_ALL - 接收目的mac是本机的所有类型数据帧,同时还可以接收本机发出的所有数据帧,混杂模式打开时,还可以接收到目的mac不是本机的数据帧

你可能感兴趣的:(net)