C原始套接字中sendto地址的目的是什么?

10

我在我的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字段。

然而,我注意到了一些事情:

  • sin_familyAF_INET——当创建套接字时已经指定了。
  • sin_port自然是未使用的(端口不是IP的概念)。
  • 无论使用什么地址,只要它是外部地址(IP数据包指定为8.8.8.8,sin_addr指定为1.1.1.1),都没有关系。

看起来sendto中的所有额外字段实际上并没有被广泛地使用。那么,我是否必须使用sendto而不是send,还是API的疏忽?

3个回答

14
使用send函数向套接字写入数据失败,提示需要指定地址。
这是因为send()函数只能用于已连接的套接字(如此处所述)。通常情况下,您会使用send()进行TCP通信(面向连接),而sendto()可用于发送UDP数据报(无连接)。
由于您想发送“ping”数据包或更正确地说是ICMP数据报,这些明显是无连接的。因此,您必须使用sendto()函数。
sendto中似乎没有额外的字段被广泛使用。那么,为什么我必须使用sendto而不是send,是技术原因还是API设计上的疏忽?
简短的回答是:当不允许使用send()时,只有一种选择,即使用sendto()
详细的回答是:这不仅仅是API设计上的疏忽。如果您想通过使用普通套接字(例如SOCK_DGRAM)发送UDP数据报,则sendto()需要有关目标地址和端口的信息,这些信息在sockaddr_in结构中提供,对吧?内核将把该信息插入到生成的IP头中,因为sockaddr_in结构是您指定接收方的唯一位置。换句话说:在这种情况下,由于您没有提供额外的IP头,内核必须从您的结构中取出目标信息。因为sendto()函数不仅适用于UDP,还适用于原始套接字(raw sockets),所以它必须是一个更或多或少"通用"的函数,可以覆盖所有不同的用例,即使一些参数(如端口号)在最终中并没有被使用,也需要涵盖它们。
例如,通过使用IPPROTO_RAW(这自动意味着IP_HDRINCL),您表明您想要自己创建IP头。因此,sendto()的最后两个参数实际上是多余的信息,因为它们已经包含在您作为第二个参数传递给sendto()的数据缓冲区中了。请注意,即使您在原始套接字中使用IP_HDRINCL,如果将相应字段设置为0,内核也会填充您的IP数据报的源地址和校验和字段。
如果您想编写自己的ping程序,还可以将socket()函数中的最后一个参数从IPPROTO_RAW更改为IPPROTO_ICMP,让内核为您创建IP头,这样您就可以减轻一些负担。现在您可以很容易地看到两个sendto()参数*dest_addraddrlen变得重要了,因为这是您提供目标地址的唯一地方。
语言和API非常古老,并且随着时间的推移不断发展。有些API从今天的角度看可能会很奇怪,但是您不能更改旧接口而不破坏大量现有代码。有时候您只能习惯很多年甚至几十年前就定义/设计的东西。
希望这回答了您的问题。

2
send()调用是在套接字处于TCP SOCK_STREAM连接状态时使用的。
来自手册页面:
“仅当套接字处于已连接状态(因此已知预期的接收方)时才可以使用send()调用。”
由于您的应用程序显然不会连接到任何其他套接字,因此我们不能指望send()正常工作。

2
除了InvertedHeli的回答外,sendto()中传递的dest_addr将被内核用来确定使用哪个网络接口。
例如,如果dest_addr具有IP地址127.0.0.1,并且原始数据包具有目标地址8.8.8.8,则您的数据包仍将路由到lo接口。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接