【问题标题】:Unnamed unix domain socket has wrong address length未命名的 unix 域套接字的地址长度错误
【发布时间】:2018-06-11 15:40:18
【问题描述】:

在这个最小的示例程序中读取发送 unix 数据报域套接字的地址时,我得到的地址长度为 0,但 unix 域套接字的手册页指定:

unnamed: 一个未绑定到路径名的流套接字使用 bind(2) 没有名字。同样,由 socket- 创建的两个套接字 pair(2) 未命名。当一个未命名套接字的地址是 返回,它的长度是 sizeof(sa_family_t),而 sun_path 不应该 被检查。

根据此手册页,我期望未命名套接字的地址长度为 2。我是否误解了手册页,手册页是否不适用于 SOCK_DGRAM 类型的 unix 套接字,或者我只是读错了长度?

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>

const char SOCK_NAME[] = { 0, 't', 'e', 's', 't' };
// or for pathnamed socket
// const char SOCK_NAME[] = "/tmp/test.uds";

const char PAYLOAD[] = "Hello!";

int main() {
    int rx_sock = socket(AF_UNIX, SOCK_DGRAM, 0);

    if (rx_sock < 0) {
        perror("Create RX");
        exit(1);
    }

    int tx_sock = socket(AF_UNIX, SOCK_DGRAM, 0);

    if (tx_sock < 0) {
        perror("Create TX");
        exit(1);
    }

    struct sockaddr_un bind_addr;
    bind_addr.sun_family = AF_UNIX;
    memcpy(bind_addr.sun_path, SOCK_NAME, sizeof(SOCK_NAME));

    socklen_t bind_len = sizeof(sa_family_t) + sizeof(SOCK_NAME);

    if (bind(rx_sock, (const struct sockaddr *)&bind_addr, bind_len) != 0) {
        perror("Bind RX");
        exit(1);
    }

    if (sendto(tx_sock, PAYLOAD, sizeof(PAYLOAD), 0, (const struct sockaddr *)&bind_addr, bind_len) < 0) {
        perror("Sendto");
        exit(1);
    }

    // For pathnamed socket
    // unlink(SOCK_NAME);

    struct sockaddr_un recv_addr;
    socklen_t recv_len = sizeof(recv_addr);
    char buffer[1024];

    ssize_t rx_count = recvfrom(rx_sock, buffer, sizeof(buffer), 0, (struct sockaddr *)&recv_addr, &recv_len);

    if (rx_count < 0) {
        perror("Recvfrom");
        exit(1);
    }

    printf("Address size of TX on receiver side: %d\n", recv_len); // 0

    recv_len = sizeof(recv_addr);

    if (getsockname(tx_sock, (struct sockaddr *)&recv_addr, &recv_len) != 0) {
        perror("getsockname");
        exit(1);
    }

    printf("Address size of TX on sender side: %d\n", recv_len); // 2
}

【问题讨论】:

  • 次要问题:sizeof(PAYLOAD) 应该是strlen(PAYLOAD)。你在那里得到指针的大小。否则,很好的问题。看起来像一个内核错误。如果行为符合预期,则手册页需要更新。如果您将SOCK_NAME 设为像/tmp/socktest 这样的真实路径,并在bind 之前添加unlink,那么您将可以将某些东西移植到其他没有“抽象名称空间”的Unices。看看他们是否做同样的事情会很有趣。
  • has no namesun_path should not be inspected 有什么不清楚的地方?你为什么要给它写信?
  • @stark 我正在写入将接收套接字绑定到抽象地址的路径
  • @WumpusQ.Wumbley 感谢您指出这一点,我在代码中创建了 PAYLOAD 一个数组以使用正确的大小。我也尝试使用路径命名的套接字,地址在接收端的长度也为 0
  • @MaxBenson: sending 套接字未命名。接收者已经知道他们绑定的地址。 recvfrom()返回发件人地址,由于未命名,所以没有发件人地址。

标签: c linux


【解决方案1】:

您有一个未命名未绑定的 Unix 域数据报套接字 tx_sock,以及一个绑定到 bind_path 中的抽象地址的 Unix 域数据报套接字 rx_sock

注意:根据man 7 unix,未绑定的Unix域套接字在Linux中未命名,它们的地址长度为sizeof (sa_family_t)。根据 POSIX.1,unbound socket addresses are unspecified,所以我们真的需要小心我们对未绑定套接字地址及其长度的期望。在这种特殊情况下,在 Linux 中,使用手册页作为指导,未绑定的 Unix 域 datagram 套接字没有地址,因此其长度为零。 (这甚至是有道理的:零地址长度表示您无法回复发件人。使用未绑定的流套接字,有一个返回给发件人的连接,但除了连接本身之外没有其他方式回复发件人;这就是为什么在在这种情况下,地址长度不为零,sizeof (sa_family_t)。)

抽象的 Unix 域套接字地址是 Linux 的扩展;它们以 NUL 字节 (\0) 开头,在文件系统中不可见。

您使用sendto(tx_sock, msg, msg_len, 0, bind_path, bind_path_len) 发送消息。

您使用recvfrom(rx_sock, buffer, sizeof buffer, 0, &amp;recv_addr, &amp;recv_addrlen) 接收消息(recv_addrlen 正确初始化为sizeof recv_addr)。

因为tx_sock没有绑定任何地址,所以未命名未绑定:它没有地址,无法回复,因此recv_addrlen == 0

如果您添加代码以将tx_sock 绑定到某个其他抽象地址,您将在recv_addrrecv_addrlen 中收到该地址。

(我通过一个使用四个线程的测试程序验证了这种行为,其中每个线程向彼此的线程发送一条消息,并打印所有接收到的消息以及它们的来源,包括标准地址和抽象地址。一切都按照文档进行.)

【讨论】:

  • 手册页指出,未绑定且因此未命名的套接字的地址长度为 2 而不是 0。我在上面添加了对 getsockname() 的调用,这确认读取了 TX 套接字的地址TX 端返回长度 2,这是 man page 所预期的,但读取 RX 端 TX 套接字的地址返回 0。那么为什么它返回 0?
  • @MaxBenson:啊,现在我明白了混乱的原因。我编辑了我的答案,希望它更清楚。你看,术语“未命名的套接字” 术语只适用于流套接字,而不适用于数据报套接字。为了让我的答案更清楚,我使用了术语 "unbound" 来表示未连接、未绑定的套接字。简单来说:“绑定” -> 完整地址,可以响应; “未命名” -> 空地址,但仍然可以响应(因为已连接流);和“未绑定” -> 没有地址,无法响应。
  • 我想你的解释确实加起来了,但我发现这个主题的手册页令人困惑。如果数据报协议返回 0 而不是 2 的行为确实是标准化的,那么手册页应该提到这个事实。
  • @MaxBenson:它仅适用于 Linux(POSIX.1 未定义!),但手册页绝对可以/应该更好地描述行为。有easy mechanisms 用于报告这些,但困难的部分是措辞正确。知道 Kerrisks 的工作原理,我敢肯定,一个清晰的附加段落来描述这个 in patch form 会很受欢迎并很快被包含在内。不过,我不确定 my English-fu 能否胜任这项任务。
【解决方案2】:

当然,您得到的名称长度为零。

char 数组中的第一个值是0,立即将字符串终止为零字节长度:

const char SOCK_NAME[] = { 0, 't', 'e', 's', 't' };

未命名:未使用 bind(2) 绑定到路径名的流套接字没有名称。

不适用,因为您正在使用bind() 绑定套接字:

【讨论】:

  • 我显然没有在未命名的传输套接字上调用bind()
  • @AndrewHenle:绑定的是接收套接字;发送套接字不是。我最初也被这个绊倒了。
猜你喜欢
  • 2012-03-04
  • 2012-03-17
  • 2017-02-06
  • 1970-01-01
  • 2016-08-28
  • 1970-01-01
  • 2013-07-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多