通过Linux套接字发送文件描述符

德卡卡鲁克

我正在尝试通过linux套接字发送一些文件描述符,但是它不起作用。我究竟做错了什么?应该如何调试这样的东西?我尝试将perror()放到所有可能的地方,但是他们声称一切正常。这是我写的:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <fcntl.h>

void wyslij(int socket, int fd)  // send fd by socket
{
    struct msghdr msg = {0};

    char buf[CMSG_SPACE(sizeof fd)];

    msg.msg_control = buf;
    msg.msg_controllen = sizeof buf;

    struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msg);
    cmsg->cmsg_level = SOL_SOCKET;
    cmsg->cmsg_type = SCM_RIGHTS;
    cmsg->cmsg_len = CMSG_LEN(sizeof fd);

    *((int *) CMSG_DATA(cmsg)) = fd;

    msg.msg_controllen = cmsg->cmsg_len;  // why does example from man need it? isn't it redundant?

    sendmsg(socket, &msg, 0);
}


int odbierz(int socket)  // receive fd from socket
{
    struct msghdr msg = {0};
    recvmsg(socket, &msg, 0);

    struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msg);

    unsigned char * data = CMSG_DATA(cmsg);

    int fd = *((int*) data);  // here program stops, probably with segfault

    return fd;
}


int main()
{
    int sv[2];
    socketpair(AF_UNIX, SOCK_DGRAM, 0, sv);

    int pid = fork();
    if (pid > 0)  // in parent
    {
        close(sv[1]);
        int sock = sv[0];

        int fd = open("./z7.c", O_RDONLY);

        wyslij(sock, fd);

        close(fd);
    }
    else  // in child
    {
        close(sv[0]);
        int sock = sv[1];

        sleep(0.5);
        int fd = odbierz(sock);
    }

}
乔纳森·莱夫勒

史蒂文斯(et al)UNIX®网络编程,第1卷:套接字网络API描述了在第15章Unix域协议(尤其是第15.7节传递描述符)中的过程之间传输文件描述符的过程对此进行完整描述很麻烦,但是必须在Unix域套接字(AF_UNIXAF_LOCAL)上完成,并且发送方进程使用,sendmsg()而接收方使用recvmsg()

我从问题中得到了经过轻微修改(和检测)的代码版本,可以在带有GCC 4.9.1的Mac OS X 10.10.1 Yosemite上为我工作:

#include "stderr.h"
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

static
void wyslij(int socket, int fd)  // send fd by socket
{
    struct msghdr msg = { 0 };
    char buf[CMSG_SPACE(sizeof(fd))];
    memset(buf, '\0', sizeof(buf));
    struct iovec io = { .iov_base = "ABC", .iov_len = 3 };

    msg.msg_iov = &io;
    msg.msg_iovlen = 1;
    msg.msg_control = buf;
    msg.msg_controllen = sizeof(buf);

    struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msg);
    cmsg->cmsg_level = SOL_SOCKET;
    cmsg->cmsg_type = SCM_RIGHTS;
    cmsg->cmsg_len = CMSG_LEN(sizeof(fd));

    *((int *) CMSG_DATA(cmsg)) = fd;

    msg.msg_controllen = CMSG_SPACE(sizeof(fd));

    if (sendmsg(socket, &msg, 0) < 0)
        err_syserr("Failed to send message\n");
}

static
int odbierz(int socket)  // receive fd from socket
{
    struct msghdr msg = {0};

    char m_buffer[256];
    struct iovec io = { .iov_base = m_buffer, .iov_len = sizeof(m_buffer) };
    msg.msg_iov = &io;
    msg.msg_iovlen = 1;

    char c_buffer[256];
    msg.msg_control = c_buffer;
    msg.msg_controllen = sizeof(c_buffer);

    if (recvmsg(socket, &msg, 0) < 0)
        err_syserr("Failed to receive message\n");

    struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msg);

    unsigned char * data = CMSG_DATA(cmsg);

    err_remark("About to extract fd\n");
    int fd = *((int*) data);
    err_remark("Extracted fd %d\n", fd);

    return fd;
}

int main(int argc, char **argv)
{
    const char *filename = "./z7.c";

    err_setarg0(argv[0]);
    err_setlogopts(ERR_PID);
    if (argc > 1)
        filename = argv[1];
    int sv[2];
    if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sv) != 0)
        err_syserr("Failed to create Unix-domain socket pair\n");

    int pid = fork();
    if (pid > 0)  // in parent
    {
        err_remark("Parent at work\n");
        close(sv[1]);
        int sock = sv[0];

        int fd = open(filename, O_RDONLY);
        if (fd < 0)
            err_syserr("Failed to open file %s for reading\n", filename);

        wyslij(sock, fd);

        close(fd);
        nanosleep(&(struct timespec){ .tv_sec = 1, .tv_nsec = 500000000}, 0);
        err_remark("Parent exits\n");
    }
    else  // in child
    {
        err_remark("Child at play\n");
        close(sv[0]);
        int sock = sv[1];

        nanosleep(&(struct timespec){ .tv_sec = 0, .tv_nsec = 500000000}, 0);

        int fd = odbierz(sock);
        printf("Read %d!\n", fd);
        char buffer[256];
        ssize_t nbytes;
        while ((nbytes = read(fd, buffer, sizeof(buffer))) > 0)
            write(1, buffer, nbytes);
        printf("Done!\n");
        close(fd);
    }
    return 0;
}

已检测但未修复的原始代码版本的输出为:

$ ./fd-passing
fd-passing: pid=1391: Parent at work
fd-passing: pid=1391: Failed to send message
error (40) Message too long
fd-passing: pid=1392: Child at play
$ fd-passing: pid=1392: Failed to receive message
error (40) Message too long

请注意,父级在子级之前完成,因此提示出现在输出的中间。

“固定”代码的输出为:

$ ./fd-passing
fd-passing: pid=1046: Parent at work
fd-passing: pid=1048: Child at play
fd-passing: pid=1048: About to extract fd
fd-passing: pid=1048: Extracted fd 3
Read 3!
This is the file z7.c.
It isn't very interesting.
It isn't even C code.
But it is used by the fd-passing program to demonstrate that file
descriptors can indeed be passed between sockets on occasion.
Done!
fd-passing: pid=1046: Parent exits
$

主要的重大变化是将都添加struct iovecstruct msghdr两个函数中的数据,并在接收函数(odbierz())中为控制消息提供了空间我报告了调试的一个中间步骤,在该步骤中,我向struct iovec父级提供了,并且父级的“消息过长”错误已消除。为了证明它是可行的(传递了文件描述符),我添加了代码以从传递的文件描述符中读取和打印文件。原始代码具有sleep(0.5)但由于sleep()采用了无符号整数,所以这相当于不休眠。我使用C99复合文字让孩子睡了0.5秒。父级睡眠1.5秒,以便在父级退出之前完成子级的输出。我可以使用wait()waitpid() 也是如此,但是太懒了。

我没有回去检查所有添加的内容是否必要。

"stderr.h"报头声明err_*()功能。这是我编写的代码(1987年之前的第一个版本),用于简洁地报告错误。err_setlogopts(ERR_PID)呼叫在所有消息之前加上PID。对于时间戳,err_setlogopts(ERR_PID|ERR_STAMP)也可以做。

对齐问题

名义动物评论中建议

我是否建议您修改代码以使用复制描述符intmemcpy()而不是直接访问数据?它不一定正确对齐(这就是手册页示例也要使用的原因)memcpy(),并且在许多Linux体系结构中,未对齐的int访问会导致问题(多达SIGBUS信号会杀死进程)。

而且不仅是Linux体系结构:SPARC和Power都需要对齐的数据,并且通常分别运行Solaris和AIX。曾几何时,DEC Alpha也要求这样做,但是这些天在现场很少见到它们。

cmsg(3)与此相关的手册页中的代码是:

struct msghdr msg = {0};
struct cmsghdr *cmsg;
int myfds[NUM_FD]; /* Contains the file descriptors to pass. */
char buf[CMSG_SPACE(sizeof myfds)];  /* ancillary data buffer */
int *fdptr;

msg.msg_control = buf;
msg.msg_controllen = sizeof buf;
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(int) * NUM_FD);
/* Initialize the payload: */
fdptr = (int *) CMSG_DATA(cmsg);
memcpy(fdptr, myfds, NUM_FD * sizeof(int));
/* Sum of the length of all control messages in the buffer: */
msg.msg_controllen = CMSG_SPACE(sizeof(int) * NUM_FD);

对to的赋值fdptr似乎假设CMSG_DATA(cmsg)已经足够对齐,可以转换为an,int *并且thememcpy()的使用NUM_FD不只是1的假设。如此说来,它应该指向数组buf,并且可能不够好按照名义动物的建议对齐,因此在我看来,这fdptr只是闯入者,如果使用以下示例会更好:

memcpy(CMSG_DATA(cmsg), myfds, NUM_FD * sizeof(int));

然后在接收端进行相反的处理将是适当的。该程序仅传递单个文件描述符,因此代码可修改为:

memmove(CMSG_DATA(cmsg), &fd, sizeof(fd));  // Send
memmove(&fd, CMSG_DATA(cmsg), sizeof(fd));  // Receive

我似乎也想起了各种操作系统上的历史问题,这些操作系统带有辅助数据,没有正常的有效载荷数据,也通过发送至少一个哑字节来避免,但是我找不到任何要验证的引用,因此我可能会记错。

鉴于Mac OS X(基于Darwin / BSD)至少需要一个struct iovec,即使它描述的是零长度消息,我也愿意相信上面显示的代码(包括3字节消息)是在正确的总体方向上迈出的重要一步。该消息可能应该是一个空字节,而不是3个字母。

我将代码修改为如下所示。它用于memmove()cmsg缓冲区中复制文件描述符它传输单个消息字节,即空字节。

在将文件描述符传递给子进程之前,父进程还读取(最多)文件的32个字节。孩子继续在父母离开的地方读书。这表明传输的文件描述符包括文件偏移量。

接收者应cmsg在将其视为文件描述符传递消息之前对进行更多验证

#include "stderr.h"
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

static
void wyslij(int socket, int fd)  // send fd by socket
{
    struct msghdr msg = { 0 };
    char buf[CMSG_SPACE(sizeof(fd))];
    memset(buf, '\0', sizeof(buf));

    /* On Mac OS X, the struct iovec is needed, even if it points to minimal data */
    struct iovec io = { .iov_base = "", .iov_len = 1 };

    msg.msg_iov = &io;
    msg.msg_iovlen = 1;
    msg.msg_control = buf;
    msg.msg_controllen = sizeof(buf);

    struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msg);
    cmsg->cmsg_level = SOL_SOCKET;
    cmsg->cmsg_type = SCM_RIGHTS;
    cmsg->cmsg_len = CMSG_LEN(sizeof(fd));

    memmove(CMSG_DATA(cmsg), &fd, sizeof(fd));

    msg.msg_controllen = CMSG_SPACE(sizeof(fd));

    if (sendmsg(socket, &msg, 0) < 0)
        err_syserr("Failed to send message\n");
}

static
int odbierz(int socket)  // receive fd from socket
{
    struct msghdr msg = {0};

    /* On Mac OS X, the struct iovec is needed, even if it points to minimal data */
    char m_buffer[1];
    struct iovec io = { .iov_base = m_buffer, .iov_len = sizeof(m_buffer) };
    msg.msg_iov = &io;
    msg.msg_iovlen = 1;

    char c_buffer[256];
    msg.msg_control = c_buffer;
    msg.msg_controllen = sizeof(c_buffer);

    if (recvmsg(socket, &msg, 0) < 0)
        err_syserr("Failed to receive message\n");

    struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);

    err_remark("About to extract fd\n");
    int fd;
    memmove(&fd, CMSG_DATA(cmsg), sizeof(fd));
    err_remark("Extracted fd %d\n", fd);

    return fd;
}

int main(int argc, char **argv)
{
    const char *filename = "./z7.c";

    err_setarg0(argv[0]);
    err_setlogopts(ERR_PID);
    if (argc > 1)
        filename = argv[1];
    int sv[2];
    if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sv) != 0)
        err_syserr("Failed to create Unix-domain socket pair\n");

    int pid = fork();
    if (pid > 0)  // in parent
    {
        err_remark("Parent at work\n");
        close(sv[1]);
        int sock = sv[0];

        int fd = open(filename, O_RDONLY);
        if (fd < 0)
            err_syserr("Failed to open file %s for reading\n", filename);

        /* Read some data to demonstrate that file offset is passed */
        char buffer[32];
        int nbytes = read(fd, buffer, sizeof(buffer));
        if (nbytes > 0)
            err_remark("Parent read: [[%.*s]]\n", nbytes, buffer);

        wyslij(sock, fd);

        close(fd);
        nanosleep(&(struct timespec){ .tv_sec = 1, .tv_nsec = 500000000}, 0);
        err_remark("Parent exits\n");
    }
    else  // in child
    {
        err_remark("Child at play\n");
        close(sv[0]);
        int sock = sv[1];

        nanosleep(&(struct timespec){ .tv_sec = 0, .tv_nsec = 500000000}, 0);

        int fd = odbierz(sock);
        printf("Read %d!\n", fd);
        char buffer[256];
        ssize_t nbytes;
        while ((nbytes = read(fd, buffer, sizeof(buffer))) > 0)
            write(1, buffer, nbytes);
        printf("Done!\n");
        close(fd);
    }
    return 0;
}

并运行一个示例:

$ ./fd-passing
fd-passing: pid=8000: Parent at work
fd-passing: pid=8000: Parent read: [[This is the file z7.c.
It isn't ]]
fd-passing: pid=8001: Child at play
fd-passing: pid=8001: About to extract fd
fd-passing: pid=8001: Extracted fd 3
Read 3!
very interesting.
It isn't even C code.
But it is used by the fd-passing program to demonstrate that file
descriptors can indeed be passed between sockets on occasion.
And, with the fully working code, it does indeed seem to work.
Extended testing would have the parent code read part of the file, and
then demonstrate that the child codecontinues where the parent left off.
That has not been coded, though.
Done!
fd-passing: pid=8000: Parent exits
$

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

Ubuntu Linux使用Unix域套接字发送文件描述符

来自分类Dev

通过文件描述符构造套接字对象

来自分类Dev

Linux套接字错误文件描述符

来自分类Dev

如何确定文件描述符是否附加到Linux中的文件或套接字

来自分类Dev

如何确定文件描述符是否附加到Linux中的文件或套接字

来自分类Dev

通过套接字文件描述符上的ioctl调用获取数据包时间戳

来自分类Dev

文件描述符和套接字文件描述符之间的区别

来自分类Dev

Linux 套接字文件描述符是否通常适用于散列

来自分类Dev

套接字编程:“接受:错误的文件描述符”

来自分类Dev

跟踪套接字(文件)描述符回到绑定地址

来自分类Dev

接受的套接字上的“错误文件描述符”错误

来自分类Dev

什么是文件描述符/连接/套接字/ ip?

来自分类Dev

接受的套接字上的“错误文件描述符”错误

来自分类Dev

当我在套接字文件描述符上调用close时,关闭了多少个文件描述符?

来自分类Dev

当我在套接字文件描述符上调用close时,关闭了多少个文件描述符?

来自分类Dev

从C中的套接字文件描述符读取时如何检测定界符?

来自分类Dev

在Linux上的C程序中从套接字描述符获取struct socket *,struct sock *

来自分类Dev

python使用套接字打开所有文件描述符

来自分类Dev

Unix域套接字传递文件描述符-sendmsg:传输端点未连接

来自分类Dev

epoll-轮询多个文件描述符(即套接字)

来自分类Dev

C套接字从接受返回的文件描述符中获取IP地址

来自分类Dev

套接字文件描述符在不同进程之间是相同的

来自分类Dev

用于管理套接字文件描述符的智能指针

来自分类Dev

setsockopt()是否等效于非套接字文件描述符?

来自分类Dev

每个客户端可以有多个套接字文件描述符吗?

来自分类Dev

什么是网络套接字的文件描述符?以及如何获得?

来自分类Dev

用于管理套接字文件描述符的智能指针

来自分类Dev

套接字错误[Errno 9]错误的文件描述符HTTP服务器

来自分类Dev

确定描述符是Windows上的套接字还是常规文件?

Related 相关文章

  1. 1

    Ubuntu Linux使用Unix域套接字发送文件描述符

  2. 2

    通过文件描述符构造套接字对象

  3. 3

    Linux套接字错误文件描述符

  4. 4

    如何确定文件描述符是否附加到Linux中的文件或套接字

  5. 5

    如何确定文件描述符是否附加到Linux中的文件或套接字

  6. 6

    通过套接字文件描述符上的ioctl调用获取数据包时间戳

  7. 7

    文件描述符和套接字文件描述符之间的区别

  8. 8

    Linux 套接字文件描述符是否通常适用于散列

  9. 9

    套接字编程:“接受:错误的文件描述符”

  10. 10

    跟踪套接字(文件)描述符回到绑定地址

  11. 11

    接受的套接字上的“错误文件描述符”错误

  12. 12

    什么是文件描述符/连接/套接字/ ip?

  13. 13

    接受的套接字上的“错误文件描述符”错误

  14. 14

    当我在套接字文件描述符上调用close时,关闭了多少个文件描述符?

  15. 15

    当我在套接字文件描述符上调用close时,关闭了多少个文件描述符?

  16. 16

    从C中的套接字文件描述符读取时如何检测定界符?

  17. 17

    在Linux上的C程序中从套接字描述符获取struct socket *,struct sock *

  18. 18

    python使用套接字打开所有文件描述符

  19. 19

    Unix域套接字传递文件描述符-sendmsg:传输端点未连接

  20. 20

    epoll-轮询多个文件描述符(即套接字)

  21. 21

    C套接字从接受返回的文件描述符中获取IP地址

  22. 22

    套接字文件描述符在不同进程之间是相同的

  23. 23

    用于管理套接字文件描述符的智能指针

  24. 24

    setsockopt()是否等效于非套接字文件描述符?

  25. 25

    每个客户端可以有多个套接字文件描述符吗?

  26. 26

    什么是网络套接字的文件描述符?以及如何获得?

  27. 27

    用于管理套接字文件描述符的智能指针

  28. 28

    套接字错误[Errno 9]错误的文件描述符HTTP服务器

  29. 29

    确定描述符是Windows上的套接字还是常规文件?

热门标签

归档