通过 Linux 套接字发送文件描述符 [英] Sending file descriptor by Linux socket

查看:30
本文介绍了通过 Linux 套接字发送文件描述符的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

#include #include #include #include #include #include #include #include void wyslij(int socket, int fd)//通过套接字发送 fd{struct msghdr msg = {0};char buf[CMSG_SPACE(sizeof fd)];msg.msg_control = buf;msg.msg_controllen = 缓冲区大小;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;//为什么 man 的例子需要它?不是多余的吗?sendmsg(socket, &msg, 0);}int odbierz(int socket)//从套接字接收 fd{struct msghdr msg = {0};recvmsg(socket, &msg, 0);struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msg);无符号字符 * 数据 = CMSG_DATA(cmsg);int fd = *((int*) 数据);//这里程序停止,可能是段错误返回 fd;}int main(){int sv[2];套接字对(AF_UNIX,SOCK_DGRAM,0,SV);int pid = fork();if (pid > 0)//在父级{关闭(SV [1]);int 袜子 = sv[0];int fd = open("./z7.c", O_RDONLY);wyslij(袜子,FD);关闭(FD);}else//在孩子中{关闭(sv[0]);int 袜子 = sv[1];睡眠(0.5);int fd = odbierz(袜子);}}

解决方案

Stevens (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 #include #include #include #include #include #include #include 静止的void wyslij(int socket, int fd)//通过套接字发送 fd{struct msghdr msg = { 0 };char buf[CMSG_SPACE(sizeof(fd))];memset(buf, '', sizeof(buf));结构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("发送消息失败
");}静止的int odbierz(int socket)//从套接字接收 fd{struct msghdr msg = {0};字符 m_buffer[256];结构iovec io = { .iov_base = m_buffer, .iov_len = sizeof(m_buffer) };msg.msg_iov = &io;msg.msg_iovlen = 1;字符 c_buffer[256];msg.msg_control = c_buffer;msg.msg_controllen = sizeof(c_buffer);if (recvmsg(socket, &msg, 0) <0)err_syserr("接收消息失败
");struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msg);无符号字符 * 数据 = CMSG_DATA(cmsg);err_remark("即将提取fd
");int fd = *((int*) 数据);err_remark("提取的 fd %d
", fd);返回 fd;}int main(int argc, char **argv){const char *filename = "./z7.c";err_setarg0(argv[0]);err_setlogopts(ERR_PID);如果 (argc > 1)文件名 = argv[1];int sv[2];如果(套接字对(AF_UNIX,SOCK_DGRAM,0,SV)!= 0)err_syserr("无法创建 Unix 域套接字对
");int pid = fork();if (pid > 0)//在父级{err_remark("父母上班
");关闭(SV [1]);int 袜子 = sv[0];int fd = 打开(文件名,O_RDONLY);如果 (fd <0)err_syserr("无法打开文件 %s 进行读取
", filename);wyslij(袜子,FD);关闭(FD);nanosleep(&(struct timespec){ .tv_sec = 1, .tv_nsec = 500000000}, 0);err_remark("父母退出
");}else//在孩子中{err_remark("孩子在玩
");关闭(sv[0]);int 袜子 = sv[1];nanosleep(&(struct timespec){ .tv_sec = 0, .tv_nsec = 500000000}, 0);int fd = odbierz(sock);printf("读取 %d!
", fd);字符缓冲区[256];ssize_t nbytes;while ((nbytes = read(fd, buffer, sizeof(buffer))) > 0)写(1,缓冲区,nbytes);printf("完成!
");关闭(FD);}返回0;}

经过检测但未修复的原始代码版本的输出是:

$ ./fd-passingfd-passing: pid=1391: 父母在工作fd-passing: pid=1391: 发送消息失败错误 (40) 消息太长fd-passing: pid=1392: 孩子在玩耍$ fd-passing: pid=1392: 接收消息失败错误 (40) 消息太长

注意父进程在子进程之前完成,所以提示出现在输出的中间.

固定"代码的输出是:

$ ./fd-passingfd-passing: pid=1046: 父母在工作fd-passing: pid=1048: 孩子在玩耍fd-passing: pid=1048: 即将提取 fdfd-passing: pid=1048: 提取的 fd 3读3!这是文件 z7.c.这不是很有趣.它甚至不是 C 代码.但是 fd-passing 程序使用它来演示该文件有时确实可以在套接字之间传递描述符.完毕!fd-passing: pid=1046: 父退出$

主要的重大变化是将 struct iovec 添加到两个函数中 struct 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) 可以完成这项工作.

对齐问题

名义动物评论:

<块引用>

我可以建议您修改代码以使用 memcpy() 复制描述符 int 而不是直接访问数据吗?它不一定正确对齐 —这就是为什么手册页示例也使用 memcpy() —并且在许多 Linux 架构中,未对齐的 int 访问会导致问题(高达 SIGBUS 信号杀死进程).

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

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

struct msghdr msg = {0};struct cmsghdr *cmsg;int myfds[NUM_FD];/* 包含要传递的文件描述符.*/char buf[CMSG_SPACE(sizeof myfds)];/* 辅助数据缓冲区 */int * fdptr;msg.msg_control = buf;msg.msg_controllen = 缓冲区大小;cmsg = CMSG_FIRSTHDR(&msg);cmsg->cmsg_level = SOL_SOCKET;cmsg->cmsg_type = SCM_RIGHTS;cmsg->cmsg_len = CMSG_LEN(sizeof(int) * NUM_FD);/* 初始化有效载荷:*/fdptr = (int *) CMSG_DATA(cmsg);memcpy(fdptr,myfds,NUM_FD * sizeof(int));/* 缓冲区中所有控制消息的长度总和:*/msg.msg_controllen = CMSG_SPACE(sizeof(int) * NUM_FD);

fdptr 的赋值似乎假设 CMSG_DATA(cmsg) 对齐得足够好,可以转换为 int *memcpy() 用于假设 NUM_FD 不只是 1.话虽如此,它应该指向数组 buf,并且这可能没有像 Nominal Animal 建议的那样充分对齐,所以在我看来 fdptr 只是一个闯入者,如果使用示例会更好:

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

然后在接收端进行相反的过程将是合适的.这个程序只传递一个文件描述符,所以代码可以修改为:

memmove(CMSG_DATA(cmsg), &fd, sizeof(fd));//发送memmove(&fd, CMSG_DATA(cmsg), sizeof(fd));//收到

<块引用>

我似乎还记得各种操作系统上的历史问题 w.r.t.没有正常负载数据的辅助数据,通过发送至少一个虚拟字节来避免,但我找不到任何参考来验证,所以我可能记错了.

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

我已将代码修改为如下所示.它使用 memmove() 将文件描述符复制到 cmsg 缓冲区和从 cmsg 缓冲区复制.它传输单个消息字节,这是一个空字节.

它还让父进程在将文件描述符传递给子进程之前读取(最多)32 个字节的文件.孩子从父母停下的地方继续阅读.这表明传输的文件描述符包括文件偏移量.

接收方应该对 cmsg 进行更多的验证,然后再将其视为文件描述符传递消息.

#include "stderr.h"#include #include #include #include #include #include #include #include 静止的void wyslij(int socket, int fd)//通过套接字发送 fd{struct msghdr msg = { 0 };char buf[CMSG_SPACE(sizeof(fd))];memset(buf, '', sizeof(buf));/* 在 Mac OS X 上,需要 struct iovec,即使它指向最少的数据 */结构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("发送消息失败
");}静止的int odbierz(int socket)//从套接字接收 fd{struct msghdr msg = {0};/* 在 Mac OS X 上,需要 struct iovec,即使它指向最少的数据 */字符 m_buffer[1];结构iovec io = { .iov_base = m_buffer, .iov_len = sizeof(m_buffer) };msg.msg_iov = &io;msg.msg_iovlen = 1;字符 c_buffer[256];msg.msg_control = c_buffer;msg.msg_controllen = sizeof(c_buffer);if (recvmsg(socket, &msg, 0) <0)err_syserr("接收消息失败
");struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);err_remark("即将提取fd
");国际金融;memmove(&fd, CMSG_DATA(cmsg), sizeof(fd));err_remark("提取的 fd %d
", fd);返回 fd;}int main(int argc, char **argv){const char *filename = "./z7.c";err_setarg0(argv[0]);err_setlogopts(ERR_PID);如果 (argc > 1)文件名 = argv[1];int sv[2];如果(套接字对(AF_UNIX,SOCK_DGRAM,0,SV)!= 0)err_syserr("无法创建 Unix 域套接字对
");int pid = fork();if (pid > 0)//在父级{err_remark("父母上班
");关闭(SV [1]);int 袜子 = sv[0];int fd = 打开(文件名,O_RDONLY);如果 (fd <0)err_syserr("无法打开文件 %s 进行读取
", filename);/* 读取一些数据以证明文件偏移量被传递 */字符缓冲区[32];int nbytes = read(fd, buffer, sizeof(buffer));如果(nbytes > 0)err_remark("父读取:[[%.*s]]
", nbytes, buffer);wyslij(袜子,FD);关闭(FD);nanosleep(&(struct timespec){ .tv_sec = 1, .tv_nsec = 500000000}, 0);err_remark("父母退出
");}else//在孩子中{err_remark("孩子在玩
");关闭(sv[0]);int 袜子 = sv[1];nanosleep(&(struct timespec){ .tv_sec = 0, .tv_nsec = 500000000}, 0);int fd = odbierz(袜子);printf("读取 %d!
", fd);字符缓冲区[256];ssize_t nbytes;while ((nbytes = read(fd, buffer, sizeof(buffer))) > 0)写(1,缓冲区,nbytes);printf("完成!
");关闭(FD);}返回0;}

还有一个示例运行:

$ ./fd-passingfd-passing: pid=8000: 父母在工作fd-passing: pid=8000: Parent read: [[这是文件z7.c.不是]]fd-passing: pid=8001: 孩子在玩耍fd-passing: pid=8001: 即将提取 fdfd-passing: pid=8001: 提取的 fd 3读3!很有意思.它甚至不是 C 代码.但是 fd-passing 程序使用它来演示该文件有时确实可以在套接字之间传递描述符.而且,有了完全工作的代码,它似乎确实有效.扩展测试将使父代码读取文件的一部分,并且然后证明子代码在父代码停止的地方继续.不过,这还没有被编码.完毕!fd-passing: pid=8000: 父退出$

I am trying to send some file descriptor by linux socket, but it does not work. What am I doing wrong? How is one supposed to debug something like this? I tried putting perror() everywhere it's possible, but they claimed that everything is ok. Here is what I've written:

#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);
    }

}

解决方案

Stevens (et al) UNIX® Network Programming, Vol 1: The Sockets Networking API describes the process of transferring file descriptors between processes in Chapter 15 Unix Domain Protocols and specifically §15.7 Passing Descriptors. It's fiddly to describe in full, but it must be done on a Unix domain socket (AF_UNIX or AF_LOCAL), and the sender process uses sendmsg() while the receiver uses recvmsg().

I got this mildly modified (and instrumented) version of the code from the question to work for me on Mac OS X 10.10.1 Yosemite with GCC 4.9.1:

#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, '', 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
");
}

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
");

    struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msg);

    unsigned char * data = CMSG_DATA(cmsg);

    err_remark("About to extract fd
");
    int fd = *((int*) data);
    err_remark("Extracted fd %d
", 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
");

    int pid = fork();
    if (pid > 0)  // in parent
    {
        err_remark("Parent at work
");
        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
", filename);

        wyslij(sock, fd);

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

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

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

The output from the instrumented but unfixed version of the original code was:

$ ./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

Note that the parent finished before the child, so the prompt appeared in the middle of the output.

The output from the 'fixed' code was:

$ ./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
$

The primary significant changes were adding the struct iovec to the data in the struct msghdr in both functions, and providing space in the receive function (odbierz()) for the control message. I reported an intermediate step in debugging where I provided the struct iovec to the parent and the parent's "message too long" error was removed. To prove it was working (a file descriptor was passed), I added code to read and print the file from the passed file descriptor. The original code had sleep(0.5) but since sleep() takes an unsigned integer, this was equivalent to not sleeping. I used C99 compound literals to have the child sleep for 0.5 seconds. The parent sleeps for 1.5 seconds so that the output from the child is complete before the parent exits. I could use wait() or waitpid() too, but was too lazy to do so.

I have not gone back and checked that all the additions were necessary.

The "stderr.h" header declares the err_*() functions. It's code I wrote (first version before 1987) to report errors succinctly. The err_setlogopts(ERR_PID) call prefixes all messages with the PID. For timestamps too, err_setlogopts(ERR_PID|ERR_STAMP) would do the job.

Alignment issues

Nominal Animal suggests in a comment:

May I suggest you modify the code to copy the descriptor int using memcpy() instead of accessing the data directly? It is not necessarily correctly aligned — which is why the man page example also uses memcpy() — and there are many Linux architectures where unaligned int access causes problems (up to SIGBUS signal killing the process).

And not only Linux architectures: both SPARC and Power require aligned data and often run Solaris and AIX respectively. Once upon a time, DEC Alpha required that too, but they're seldom seen in the field these days.

The code in the manual page cmsg(3) related to this is:

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);

The assignment to fdptr appears to assume that CMSG_DATA(cmsg) is sufficiently well aligned to be converted to an int * and the memcpy() is used on the assumption that NUM_FD is not just 1. With that said, it is supposed to be pointing at the array buf, and that might not be sufficiently well aligned as Nominal Animal suggests, so it seems to me that the fdptr is just an interloper and it would be better if the example used:

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

And the reverse process on the receiving end would then be appropriate. This program only passes a single file descriptor, so the code is modifiable to:

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

I also seem to recall historical issues on various OSes w.r.t. ancillary data with no normal payload data, avoided by sending at least one dummy byte too, but I cannot find any references to verify, so I might remember wrong.

Given that Mac OS X (which has a Darwin/BSD basis) requires at least one struct iovec, even if that describes a zero-length message, I'm willing to believe that the code shown above, which includes a 3-byte message, is a good step in the right general direction. The message should perhaps be a single null byte instead of 3 letters.

I've revised the code to read as shown below. It uses memmove() to copy the file descriptor to and from the cmsg buffer. It transfers a single message byte, which is a null byte.

It also has the parent process read (up to) 32 bytes of the file before passing the file descriptor to the child. The child continues reading where the parent left off. This demonstrates that the file descriptor transferred includes the file offset.

The receiver should do more validation on the cmsg before treating it as a file descriptor passing message.

#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, '', 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
");
}

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
");

    struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);

    err_remark("About to extract fd
");
    int fd;
    memmove(&fd, CMSG_DATA(cmsg), sizeof(fd));
    err_remark("Extracted fd %d
", 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
");

    int pid = fork();
    if (pid > 0)  // in parent
    {
        err_remark("Parent at work
");
        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
", 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]]
", nbytes, buffer);

        wyslij(sock, fd);

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

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

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

And a sample run:

$ ./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
$

这篇关于通过 Linux 套接字发送文件描述符的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆