微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何使用 QUdpSocket 写入端口 0?

如何解决如何使用 QUdpSocket 写入端口 0?

我想使用 Qt 实现 WoL Magic Packet,以便在 GNU/Linux 和 Microsoft Windows 之间进行移植。维基百科说:“通常作为 UDP 数据报发送到端口 0(保留端口号)、7(回声协议)或 9(丢弃协议)”,但我无法在端口上写入任何数据0 带 QUdpSocket,为什么?

问题示例

QUdpSocket  socket;
auto const  writtenSize = socket.writeDatagram(toSend,magicPacketLength,QHostAddress(ip),defaultPort);

if (writtenSize != magicPacketLength)
{
  result = { false,"writtenSize(" + QString::number(writtenSize)
             + ") != magicPacketLength(" + QString::number(magicPacketLength) + "): "
             + socket.errorString() };
}

输出为:

writtenSize(-1) != magicPacketLength(102): Unable to send a message

其他端口(7 和 9)都可以,但是为什么我无法向端口 0 写入数据?

解决方法

注意:这个答案只考虑了 Linux,但同样应该适用于任何根据 IETF RFC 实现 UDP 的其他系统。

TL;DR:使用 connectToHostwrite

你必须QUdpSocket::connectToHost然后QIODevice::write,例如

QUdpSocket socket;
socket.connectToHost(target_address,0);

socket.write(magic_datagram,magic_datagram_size);

这是由于 sendmsg 的 Linux 内核实现。但是,鉴于 sendmsgconnect+send(或 connectToHostwrite)的行为可能没有区别,您不应该计算 {{ 1}} 和“写”永远工作。毕竟,WoL 是一个以太网框架。

为什么 connectToHost 会失败?

沿着网络栈走

IANA assigns ports to both UDP and TCP。我们的目标端口 QUdpSocket::sendTo 在 IANA 的注册中列为保留。这是很自然的,因为源端口零在 the UDP specification 中明确定义为“未使用”。

然而,保留值很少阻止我们直接输入它,而 Qt 很高兴地接受了它。因此,沿途的某些事情必须阻止我们实际发送数据报。

我们的数据报在最终进入线路之前遍历了几个层:

  1. Qt 的网络堆栈
  2. GNU C 库的 (glibc) 套接字(通常只是内核周围的一小层)
  3. Linux 内核
  4. 网卡(那时真的不应该在意)

Qt 的错误管理和 C 风格的错误

在深入研究这个问题之前,我们应该首先通过0errno检查第二层是否有更多信息:

perror()

这确实会报告

if (writtenSize != magicPacketLength)
{
  if(errno) 
  {
    int err = errno;
    perror("Underlying error in UDP");
    fprintf(stderr "Error number: %d\n",err);
  }
  result = { false,"writtenSize(" + QString::number(writtenSize)
             + ") != magicPacketLength(" + QString::number(magicPacketLength) + "): "
             + socket.errorString() };
}

错误 22 是 Underlying error in UDP: Invalid argument Error number: 22 ,一个无效的参数。由于 Qt 通常报告错误的参数很好(而不仅仅是“无法发送消息”),我们可以跳过它的实现,转而查看 glibc 甚至内核。

我们也可以在没有 Qt 的情况下重新创建行为:

-EINVAL

因此,我们走在正确的轨道上。但是,如果您对 Qt 的网络堆栈感兴趣,请查看

深入深渊

现在让我们完全跳过 glibc 而直接进入内核。由于我们在 IPv4 中处理 UDP,因此我们需要进入 /net/ipv4/udp.c。正如我们已经知道我们得到 int main(int argc,char* argv[]) { int sockfd; int not_ok = 0; struct sockaddr_in servaddr; sockfd = socket(AF_INET,SOCK_DGRAM,0) memset(&servaddr,sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_aton("192.168.11.31",&servaddr.sin_addr); // Use port 0 on no args,port 9 on any arg servaddr.sin_port = htons(argc > 1 ? 9 : 0 ); sendto(sockfd,"",MSG_CONFIRM,(const struct sockaddr *) &servaddr,sizeof(servaddr)); if(errno) { int err = errno; perror("Error during sendto"); printf("Errno: %d\n",err); } } ,我们可以简单地 search for the error and find:

EINVAL

Linux 内核识别保留端口并在 // Note: if `usin` is valid than an destination was given to sendto. // This is true for messages sent via QUdpSocket::sendTo. if (usin) { if (msg->msg_namelen < sizeof(*usin)) return -EINVAL; if (usin->sin_family != AF_INET) { if (usin->sin_family != AF_UNSPEC) return -EAFNOSUPPORT; } daddr = usin->sin_addr.s_addr; dport = usin->sin_port; if (dport == 0) return -EINVAL; } 中将其拒绝为无效。虽然这似乎是错误的函数,但 udp_sendmsg 系统调用是根据 sendto 实现的,它在 UDP 套接字上调用 socket_sendmsg

因此,我们无法通过 udp_sendmsg 发送任何 UDP 数据包。

通过 QUdpSocket::sendTo 的替代方案

现在,QUdpSocket::connectToHost 有了替代方案。如果我们知道我们要将所有消息发送到同一个端口,那么我们可以使用 QUdpSocket::sendTo 来避免重复:

connectToHost

如果我们尝试这个变体,我们会立即得到正确的结果。为什么?

QByteArray payload; QUdpSocket socket; socket.connectToHost(target_address,0); socket.write(payload); 使用 QUdpSocket::connectToHost 系统调用。 connect 系统调用不返回 EINVAL(至少到 4.15,没有检查更高的)。此外,它使用 ipv4_datagram_connect,它很乐意接受任何端口。

我们还可以再次检查简单 C 中的行为:

connect

那么 int main(int argc,port 9 on any arg servaddr.sin_port = htons(argc > 1 ? 9 : 0 ); connect(sockfd,sizeof(servaddr)); send(sockfd,MSG_CONFIRM); if(errno) { int err = errno; perror("Error during sendto"); printf("Errno: %d\n",err); } } 会被 udp_sendmsgQIODevice::write 使用吗?好吧,还记得上面代码中的 send 吗?由于地址存储在套接字的当前状态,if(usin)。目标地址检查永远不会发生。这可能是一个错误,或者完全是故意的。需要检查这些文件的 usin == NULL

鉴于具有零目标端口的 git log 可能是 UDP 的常见用例,这种行为可能永远不会改变,因为它会破坏用户空间,但是,应该不要对不打算在给定协议中使用的保留端口过于信任。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。