在Linux上,可以使用AF_PACKET创建一个套接字来从套接字接收原始数据并在应用程序中进行IP过滤。但 OSX 中的手册页没有这个:
PF_LOCAL Host-internal protocols, formerly called PF_UNIX,
PF_UNIX Host-internal protocols, deprecated, use PF_LOCAL,
PF_INET Internet version 4 protocols,
PF_ROUTE Internal Routing protocol,
PF_KEY Internal key-management function,
PF_INET6 Internet version 6 protocols,
PF_SYSTEM System domain,
PF_NDRV Raw access to network device
这不是POSIX标准接口吗?如何在 OSX 上实现同样的事情?
没有任何协议是 POSIX 标准。 POSIX 不要求系统支持任何特定的网络协议或根本不支持任何网络协议。
AF_PACKET
是一个纯粹的 Linux 发明 AFAIK,你在其他系统上找不到它。
BPF(Berkley Packet Filters)也不是 POSIX,它是一个 BSD 发明,许多系统已经复制,因为它非常方便。但是,您不能用它注入流量,您只能用它捕获传入和传出流量。
如果有人关心,这里是最新的 POSIX 标准:
The Open Group 基本规范第 7 期,2018 年版
IEEE Std 1003.1™-2017(IEEE Std 1003.1-2008 的修订版)
如果您确实想发送原始 IP 数据包(无论是 IPv4 还是 IPv6),使用原始 IP 套接字是最便携的:
int soc = socket(PF_INET, SOCK_RAW, IPPROTO_IP);
然后你需要告诉系统,你想提供自己的IP标头:
int yes = 1;
setsockopt(soc, IPPROTO_IP, IP_HDRINCL, &yes, sizeof(yes));
现在您可以将原始 IP 数据包(例如 IP 标头 + UDP 标头 + 有效负载数据)发送到套接字进行发送,但是,根据您的系统,系统将执行一些健全性检查,并可能覆盖标头中的某些字段。例如。它可能不允许您创建格式错误的 IP 数据包或阻止您执行 IP 地址欺骗。因此,如果您的 IP 标头使用
0.0.0.0
或 ::
作为源地址,它可能会为您计算 IPv4 标头校验和或自动填写正确的源地址。检查目标系统上的手册页中的 ip(4)
或 raw(7)
。 Apple 不再提供 macOS 的程序员手册页,但您可以在线找到它们。
引用该手册页:
与以前的 BSD 版本不同,程序必须设置 IP 标头,包括以下内容:
ip->ip_v = IPVERSION; ip->ip_hl = hlen >> 2; ip->ip_id = 0; /* 0 means kernel set appropriate value */ ip->ip_off = offset; ip->ip_len = len;
请注意,
和ip_off
字段采用 主机字节顺序。ip_len
如果标头源地址设置为
,内核将 选择合适的地址。INADDR_ANY
请注意,根本没有提及
ip_sum
,因此显然您不必提供该值,系统将始终为您计算。
如果将其与 Linux raw(7) 进行比较:
┌───────────────────────────────────────────────────┐ │IP Header fields modified on sending by IP_HDRINCL │ ├──────────────────────┬────────────────────────────┤ │IP Checksum │ Always filled in │ ├──────────────────────┼────────────────────────────┤ │Source Address │ Filled in when zero │ ├──────────────────────┼────────────────────────────┤ │Packet ID │ Filled in when zero │ ├──────────────────────┼────────────────────────────┤ │Total Length │ Always filled in │ └──────────────────────┴────────────────────────────┘
从原始 IP 套接字接收数据时,您将获得到达主机的所有传入 IP 数据包或仅获得其中的一个子集(例如,Windows 确实支持原始套接字,但永远不会让您发送或接收 TCP 数据包)。您将收到完整的数据包,包括所有标头,因此收到的每个数据包的第一个字节是 IP 标头的第一个字节。
这里有些人会问我为什么用
IPPROTO_IP
而不用IPPROTO_RAW
。使用 IPPROTO_RAW
时,无需设置 IP_HDRINCL
:
协议意味着已启用IPPROTO_RAW
并且能够 发送在传递的标头中指定的任何 IP 协议。IP_HDRINCL
但是您只能使用
IPPROTO_RAW
进行传出流量:
套接字仅用于发送。IPPROTO_RAW
在 macOS 上,您可以使用
IPPROTO_IP
并且您将收到所有 IP 数据包,但在 Linux 上这可能不起作用,因此创建了一个新的套接字 PF_PACKET
套接字类型。在两个系统上都应该工作的是指定一个子协议:
int soc = socket(PF_INET, SOCK_RAW, IPPROTO_UDP);
当然,现在您只能通过该套接字发送/接收 UDP 数据包。如果您再次设置
IP_HDRINCL
,则需要在发送时提供完整的 IP 标头,并且您将在接收时收到完整的 IP 标头。如果您不设置它,您可以只在发送时提供 UDP 标头,系统将添加一个 IP 标头本身,也就是说,如果套接字已连接并且可选地绑定,那么系统知道在该标头中使用哪些地址。对于接收该选项不起作用,您始终会获得在此类套接字上收到的每个 UDP 数据包的 IP 标头。
如果人们想知道为什么我使用
PF_INET
而不是 AF_INET
:PF 表示 协议系列,AF 表示地址系列。通常这些是相同的(例如 AF_INET == PF_INET
),所以你使用什么并不重要,但严格来说,套接字应该使用 PF_
创建,并且 sockaddr
结构中的族应该使用 AF_
设置为一天可能有一种协议支持两种不同的地址,然后就会有 AF_XXX1
和 AF_XXX2
,并且没有一个与 PF_XXX
相同。
AF_PACKET
。相反,您应该使用 /dev/bpfX
(伯克利数据包过滤器),它允许您捕获数据包。有关更多信息,请阅读:https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man4/bpf.4.html
如果您想在 Mac OS X 上发送原始以太网帧(例如您自己的链路级协议,而不是 IP),那么您可以使用 PF_NDRV 套接字,这与 PF_RAW 类似:
#include <sys/socket.h>
#include <net/if.h>
#include <net/ndrv.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <net/ethernet.h>
int main (int argc, char **argv) {
if (geteuid()) { fprintf(stderr,"No root, no service\n"); exit(1); }
int s = socket(PF_NDRV,SOCK_RAW,0);
if (s < 0) { perror ("socket"); exit(2); }
uint16_t etherType = ntohs(atoi(argv[1]));
struct sockaddr_ndrv sa_ndrv;
strlcpy((char *)sa_ndrv.snd_name, "en0", sizeof (sa_ndrv.snd_name));
sa_ndrv.snd_family = PF_NDRV;
sa_ndrv.snd_len = sizeof (sa_ndrv);
int rc = bind(s, (struct sockaddr *) &sa_ndrv, sizeof(sa_ndrv));
if (rc < 0) { perror ("bind"); exit (3);}
char packetBuffer[2048];
#ifdef LISTENER
struct ndrv_protocol_desc desc;
struct ndrv_demux_desc demux_desc[1];
memset(&desc, '\0', sizeof(desc));
memset(&demux_desc, '\0', sizeof(demux_desc));
/* Request kernel for demuxing of one chosen ethertype */
desc.version = NDRV_PROTOCOL_DESC_VERS;
desc.protocol_family = atoi(argv[1]);
desc.demux_count = 1;
desc.demux_list = (struct ndrv_demux_desc*)&demux_desc;
demux_desc[0].type = NDRV_DEMUXTYPE_ETHERTYPE;
demux_desc[0].length = sizeof(unsigned short);
demux_desc[0].data.ether_type = ntohs(atoi(argv[1]));
if (setsockopt(s,
SOL_NDRVPROTO,
NDRV_SETDMXSPEC,
(caddr_t)&desc, sizeof(desc))) {
perror("setsockopt"); exit(4);
}
/* Socket will now receive chosen ethertype packets */
while ((rc = recv (s, packetBuffer, 2048, 0) ) > 0 ) {
printf("Got packet\n"); // remember, this is a PoC..
}
#else
memset(packetBuffer, '\xff', 12);
memcpy(packetBuffer + 12, ðerType, 2);
strcpy(packetBuffer,"NDRV is fun!");
rc = sendto (s, packetBuffer, 20, 0,
(struct sockaddr *)&sa_ndrv, sizeof(sa_ndrv));
if (rc < 0) { perror("sendto"); }
#endif
}