C原始套接字的sendto地址的用途?

Purpose of sendto address for C raw socket?

我在 linux 机器上通过 C 中的原始套接字发送一些 ping 数据包。

int sock_fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);

这意味着我在写入套接字时指定了IP包头(隐含IP_HDRINCL)。

send写入套接字失败,提示我需要指定一个地址。

如果我使用 sendto 就可以了。对于 sendto,我必须指定要使用的 sockaddr_in 结构,其中包括字段 sin_familysin_portsin_addr

不过,我注意到了一些事情:

sendto 中的额外字段似乎 none 实际使用得很大。那么,我必须使用 sendto 而不是 send 是出于技术原因,还是只是 API 中的疏忽?

当套接字处于 TCP SOCK_STREAM 连接状态时使用 send() 调用。

来自手册页:

the send() call may be used only when the socket is in a connected state (so that the intended recipient is known).

由于您的应用程序显然不与任何其他套接字连接,我们不能期望 send() 工作。

Writing to the socket with send fails, telling me I need to specify an address.

它失败了,因为 send() 函数只能用于 connected 套接字(如 here 所述)。通常您会使用 send() 进行 TCP 通信 (connection-oriented),而 sendto() 可用于发送 UDP 数据报(无连接)。

因为你想发送 "ping" 数据包,或者更准确地说是 ICMP 数据报,它们显然是无连接的,你必须使用 sendto() 函数。

It seems none of the extra fields in sendto are actually used to great extent. So, is there a technical reason why I have to use sendto instead of send or is it just an oversight in the API?

简答:

当你不被允许使用send()时,那么就只剩下一个选项了,叫做sendto()

长答案:

这不仅仅是 API 中的疏忽。如果要使用普通套接字发送 UDP 数据报(例如 SOCK_DGRAM),sendto() 需要有关目标地址和端口的信息,这些信息是您在结构体 sockaddr_in 中提供的,对吧?内核会将该信息插入到生成的 IP header 中,因为结构 sockaddr_in 唯一 指定接收者的位置。或者换句话说:在这种情况下,内核必须从您的结构中获取目标信息,因为您没有提供额外的 IP header.

因为 sendto() 不仅用于 UDP 还用于原始套接字,它必须或多或少是一个 "generic" 函数,它可以涵盖所有不同的用例,即使一些参数像端口号最后不是relevant/used.

例如,通过使用 IPPROTO_RAW(自动暗示 IP_HDRINCL),您表明您想要自己创建 IP header 的意图。因此 sendto() 的最后两个参数实际上是冗余信息,因为它们已经包含在您作为第二个参数传递给 sendto() 的数据缓冲区中。请注意,即使您对原始套接字使用 IP_HDRINCL,如果您将相应字段设置为 0,内核也会填充 IP 数据报的源地址和校验和。

如果您想编写自己的 ping 程序,您还可以将 socket() 函数中的最后一个参数从 IPPROTO_RAW 更改为 IPPROTO_ICMP 并让内核创建 IP header给你,让你少操心一件事。现在您可以轻松地看到两个 sendto() 参数 *dest_addraddrlen 如何再次变得重要,因为它是您提供目标地址的唯一地方。

语言和 API 非常古老,并且随着时间的推移而发展。从今天的角度来看,某些 API 可能看起来很奇怪,但您无法在不破坏大量现有代码的情况下更改旧界面。有时您只需要习惯 defined/designed 很多年或几十年前的事情。

希望这能回答您的问题。

除了 InvertedHeli 的回答之外,sendto() 中传递的 dest_addr 将被内核用来确定使用哪个网络接口。

例如,如果 dest_addr 具有 ip 127.0.0.1 并且原始数据包具有目标地址 8.8.8.8,您的数据包仍将被路由到 lo 接口。