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_family
、sin_port
和 sin_addr
。
不过,我注意到了一些事情:
-
sin_family
是 AF_INET
- 在创建套接字时已经指定。
sin_port
自然不用(端口不是IP的概念)
- 不管我用什么地址,只要是外部地址即可(IP包指定8.8.8.8,
sin_addr
指定1.1.1.1)。
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_addr
和 addrlen
如何再次变得重要,因为它是您提供目标地址的唯一地方。
语言和 API 非常古老,并且随着时间的推移而发展。从今天的角度来看,某些 API 可能看起来很奇怪,但您无法在不破坏大量现有代码的情况下更改旧界面。有时您只需要习惯 defined/designed 很多年或几十年前的事情。
希望这能回答您的问题。
除了 InvertedHeli 的回答之外,sendto()
中传递的 dest_addr
将被内核用来确定使用哪个网络接口。
例如,如果 dest_addr
具有 ip 127.0.0.1
并且原始数据包具有目标地址 8.8.8.8
,您的数据包仍将被路由到 lo
接口。
我在 linux 机器上通过 C 中的原始套接字发送一些 ping 数据包。
int sock_fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
这意味着我在写入套接字时指定了IP包头(隐含IP_HDRINCL
)。
用send
写入套接字失败,提示我需要指定一个地址。
如果我使用 sendto
就可以了。对于 sendto
,我必须指定要使用的 sockaddr_in
结构,其中包括字段 sin_family
、sin_port
和 sin_addr
。
不过,我注意到了一些事情:
-
sin_family
是AF_INET
- 在创建套接字时已经指定。 sin_port
自然不用(端口不是IP的概念)- 不管我用什么地址,只要是外部地址即可(IP包指定8.8.8.8,
sin_addr
指定1.1.1.1)。
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 usesendto
instead ofsend
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_addr
和 addrlen
如何再次变得重要,因为它是您提供目标地址的唯一地方。
语言和 API 非常古老,并且随着时间的推移而发展。从今天的角度来看,某些 API 可能看起来很奇怪,但您无法在不破坏大量现有代码的情况下更改旧界面。有时您只需要习惯 defined/designed 很多年或几十年前的事情。
希望这能回答您的问题。
除了 InvertedHeli 的回答之外,sendto()
中传递的 dest_addr
将被内核用来确定使用哪个网络接口。
例如,如果 dest_addr
具有 ip 127.0.0.1
并且原始数据包具有目标地址 8.8.8.8
,您的数据包仍将被路由到 lo
接口。