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

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

问题描述

我想送被Linux套接字一些文件描述符,但它不工作。我究竟做错了什么?一个是应该如何调试这样的事情?我试图把PERROR()到处是可能的,但他们声称一切正常。下面是我写的:

 的#include<&stdio.h中GT;
#包括LT&;&stdlib.h中GT;
#包括LT&;&unistd.h中GT;
#包括LT&;&string.h中GT;
#包括LT&; SYS / wait.h>
#包括LT&; SYS / socket.h中>
#包括LT&; SYS / types.h中>
#包括LT&;&fcntl.h GT;无效wyslij(INT插座,诠释FD)//通过插座发送FD
{
    结构指向msghdr味精= {0};    焦炭BUF [CMSG_SPACE(sizeof的FD);    msg.msg_control = BUF;
    msg.msg_controllen = sizeof的BUF;    cmsghdr结构* CMSG = CMSG_FIRSTHDR(安培;味精);
    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; //为什么例如从人需要它吗?是不是多余的?    SENDMSG(插座,&安培;味精,0);
}
INT odbierz(INT插座)//接收来自套接字fd
{
    结构指向msghdr味精= {0};
    recvmsg(插座,&安培;味精,0);    cmsghdr结构* CMSG = CMSG_FIRSTHDR(安培;味精);    无符号字符*数据= CMSG_DATA(CMSG);    INT FD = *((INT *)的数据); //这里程序停止,可能与段错误    返回FD;
}
诠释的main()
{
    INT SV [2];
    socketpair(AF_UNIX,SOCK_DGRAM,0,SV);    INT PID =叉();
    如果(PID大于0)//在父
    {
        关闭(SV [1]);
        INT袜子= SV [0];        INT FD =打开(./ z7.c,O_RDONLY);        wyslij(袜子,FD);        关闭(FD);
    }
    //其他儿童
    {
        关闭(SV [0]);
        INT袜子= SV [1];        睡眠(0.5);
        INT FD = odbierz(袜子);
    }}


解决方案

史蒂文斯(等)UNIX®网络编程,卷1:套接字联网API 介绍了第15章进程间传输文件描述符的 Unix领域协议的具体§15.7的描述符传递的过程的。这是繁琐的全面来形容,但它必须在Unix域套接字进行( AF_UNIX AF_LOCAL ),并发送过程中使用<一个href=\"http://pubs.opengroup.org/onlinepubs/9699919799/functions/sendmsg.html\"><$c$c>sendmsg()而接收器使用<一href=\"http://pubs.opengroup.org/onlinepubs/9699919799/functions/recvmsg.html\"><$c$c>recvmsg().

我得到了这个轻微修改(仪表)的code版本从这个问题对我来说与GCC 4.9.1的工作在Mac OS X 10.10.1约塞米蒂:

的#includestderr.h
#包括LT&;&fcntl.h GT;
#包括LT&;&stdio.h中GT;
#包括LT&;&stdlib.h中GT;
#包括LT&;&string.h中GT;
#包括LT&; SYS / socket.h中&GT;
#包括LT&; SYS / wait.h&GT;
#包括LT&;&time.h中GT;
#包括LT&;&unistd.h中GT;静态的
无效wyslij(INT插座,诠释FD)//通过插座发送FD
{
    结构指向msghdr味精= {0};
    焦炭BUF [CMSG_SPACE(的sizeof(FD))];
    memset的(BUF,'\\ 0',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);    cmsghdr结构* CMSG = CMSG_FIRSTHDR(安培;味精);
    cmsg-&GT; cmsg_level = SOL_SOCKET;
    cmsg-&GT;把cmsg_type = SCM_RIGHTS;
    cmsg-&GT; CMSG_LEN = CMSG_LEN(的sizeof(FD));    *((INT *)CMSG_DATA(CMSG))= FD;    msg.msg_controllen = cmsg-&GT; CMSG_LEN;    如果(SENDMSG(插座,&放大器;味精,0)℃的)
        err_syserr(无法发送邮件\\ n);
}静态的
INT odbierz(INT插座)//接收来自套接字fd
{
    结构指向msghdr味精= {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);    如果(recvmsg(插座,&放大器;味精,0)℃的)
        err_syserr(无法接收邮件\\ n);    cmsghdr结构* CMSG = CMSG_FIRSTHDR(安培;味精);    无符号字符*数据= CMSG_DATA(CMSG);    err_remark(关于提取FD \\ n);
    INT FD = *((INT *)的数据);
    err_remark(FD提取%d个\\ N,FD);    返回FD;
}INT主(INT ARGC,字符** argv的)
{
    为const char *文件名=./z7.c;    err_setarg0(的argv [0]);
    err_setlogopts(ERR_PID);
    如果(argc个大于1)
        文件名=的argv [1];
    INT SV [2];
    如果(socketpair(AF_UNIX,SOCK_DGRAM,0,SV)!= 0)
        err_syserr(无法创建Unix域套接字对\\ n);    INT PID =叉();
    如果(PID大于0)//在父
    {
        err_remark(父在工作\\ n);
        关闭(SV [1]);
        INT袜子= SV [0];        INT FD =开放(文件名,O_RDONLY);
        如果(FD℃,)
            err_syserr(无法打开文件%s读取\\ n,文件名);        wyslij(袜子,FD);        关闭(FD);
        了nanosleep(及(结构的timespec){.tv_sec = 1,.tv_nsec = 5亿},0);
        err_remark(父退出的\\ n);
    }
    //其他儿童
    {
        err_remark(孩子在作怪\\ n);
        关闭(SV [0]);
        INT袜子= SV [1];        了nanosleep(及(结构的timespec){.tv_sec = 0,.tv_nsec = 5亿},0);        INT FD = odbierz(袜子);
        的printf(读%d个\\ N!FD);
        字符缓冲区[256];
        ssiz​​e_t供为nbytes;
        而((为nbytes =读(FD,缓冲器,的sizeof(缓冲液)))0)
            写(1,缓冲,为nbytes);
        的printf(完成\\ n!);
        关闭(FD);
    }
    返回0;
}

从原来的code的仪表,但不固定版本的输出是:

$ ./fd-passing
FD-传:PID = 1391:家长工作
FD-传:PID = 1391:无法发送消息
错误(40)消息太长
FD-传:PID = 1392:儿童在玩耍
$ FD-传:PID = 1392:无法接收消息
错误(40)消息太长

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

从'固定'code的输出是:

$ ./fd-passing
FD-传:PID = 1046:家长工作
FD-传:PID = 1048:儿童在玩耍
FD-传:PID = 1048:关于提取FD
FD-传:PID = 1048:提取FD 3
阅读3!
这是在文件z7.c.
这是不是很有趣。
它甚至不是C $ C $℃。
但所使用的FD-传递方案证明文件
描述的确可以偶尔插座之间传递。
完成!
FD-传:PID = 1046:家长退出
$

主要显著变化添加结构iovec的在这两个功能在结构指向msghdr 的数据,在接收器功能提供空间( odbierz())的控制消息。我报调试的一个中间步骤,我提供的结构iovec的父,除去父母的消息太长的错误。为了证明这是工作(文件描述符传递),我增加code读取和传递的文件描述符打印文件。原来的code的睡眠(0.5)但因为睡眠()接受一个无符号整数,这相当于不睡觉。我用C99复合文字有孩子的睡眠0.5秒。父休眠1.5秒,使得从子的输出是父退出之前完成。我可以使用的wait() waitpid函数()过,但就是懒得这样做。

我没有回去,并检查了所有添加是必要的。

stderr.h标题声明 ERR _ *()功能。这是code我写的(1987年之前的第一个版本),以简洁报告错误。在 err_setlogopts(ERR_PID)调用prefixes与PID的所有消息。对于时间戳太, err_setlogopts。(ERR_PID | ERR_STAMP)将做的工作。

对齐问题

标称动物建议在<一个href=\"http://stackoverflow.com/questions/28003921/sending-file-descriptor-by-linux-socket/28005250?noredirect=1#comment44410434_28005250\">comment:


  

我可以建议你修改code复制描述符 INT 使用的memcpy()而不是直接访问数据?它不一定正确对齐&MDASH;这就是为什么男人页面示例还使用的memcpy()&MDASH;而且有很多的Linux体系结构,其中未对齐的 INT 访问导致问题(最多SIGBUS信号杀死进程)。


和不但Linux的架构:SPARC和强度需要对齐的数据,往往分别运行Solaris和AIX。曾几何时,DEC的Alpha要求太多,但他们很少看到在该领域的这些日子。

在code在手册中 CMSG(3)与此相关的是:

结构指向msghdr味精= {0};
cmsghdr结构* CMSG;
INT myfds [NUM_FD] / *包含文件描述符通过。 * /
焦炭BUF [CMSG_SPACE(sizeof的myfds)]; / *辅助数据缓冲区* /
INT * fdptr;msg.msg_control = BUF;
msg.msg_controllen = sizeof的BUF;
CMSG = CMSG_FIRSTHDR(安培;味精);
cmsg-&GT; cmsg_level = SOL_SOCKET;
cmsg-&GT;把cmsg_type = SCM_RIGHTS;
cmsg-&GT; CMSG_LEN = CMSG_LEN(的sizeof(int)的* NUM_FD);
/ *初始化有效载荷:* /
fdptr =(INT *)CMSG_DATA(CMSG);
的memcpy(fdptr,myfds,NUM_FD *的sizeof(INT));
/ *缓冲区中的所有控制消息的长度的总和:* /
msg.msg_controllen = cmsg-&GT; CMSG_LEN;

分配到 fdptr 似乎认为 CMSG_DATA(CMSG)已充分对准被转换为为int * 的memcpy()是假设使用了 NUM_FD 不仅仅是1.随着中说,它应该在阵列 BUF 来指向,并且可能无法充分以及标称动物暗示对齐,因此在我看来,该 fdptr 只是一个闯入者,这将是更好的,如果使用的示例:

的memcpy(CMSG_DATA(CMSG),myfds,NUM_FD *的sizeof(INT));

和在接收端的逆过程随后将是适当的。该程序只能通过单一的文件描述符,所以code是修改为:

memmove与(CMSG_DATA(CMSG),放大器; FD,的sizeof(FD)); //发送
memmove与(安培; FD,CMSG_DATA(CMSG)的sizeof(FD)); //接收


  

我还依稀记得在各种操作系统历史问题w.r.t.没有正常的有效载荷数据,通过发送过至少一个空字节避免辅助数据,但我找不到任何引用来验证,所以我可能记错。


由于Mac OS X的(其中有一个达尔文/ BSD的基础上)至少需要一个结构iovec的,即使描述长度为零的消息时,我愿意相信上面所示的code,其中包括3字节的信息,是在正确的一般方向上的良好步骤。该消息或许应该是,而不是3个字母组成一个单一的空字节。

我已经修改了code如下图所示阅读。它采用 memmove与()来的文件描述符复制到从 CMSG 缓冲区。它传输一个字节的信息,这是一个空字节。

它还具有阅读父进程(最多)32个字节的文件传递的文件描述符给孩子之前。孩子继续读书,其中母公司不放过。这表明,传送的文件描述符包括该文件偏移量

接收方应把它当作一个文件描述符传递消息之前做的 CMSG 更多的验证。

的#includestderr.h
#包括LT&;&fcntl.h GT;
#包括LT&;&stdio.h中GT;
#包括LT&;&stdlib.h中GT;
#包括LT&;&string.h中GT;
#包括LT&; SYS / socket.h中&GT;
#包括LT&; SYS / wait.h&GT;
#包括LT&;&time.h中GT;
#包括LT&;&unistd.h中GT;静态的
无效wyslij(INT插座,诠释FD)//通过插座发送FD
{
    结构指向msghdr味精= {0};
    焦炭BUF [CMSG_SPACE(的sizeof(FD))];
    memset的(BUF,'\\ 0',sizeof的(BUF));    / *在Mac OS X,则需要在结构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);    cmsghdr结构* CMSG = CMSG_FIRSTHDR(安培;味精);
    cmsg-&GT; cmsg_level = SOL_SOCKET;
    cmsg-&GT;把cmsg_type = SCM_RIGHTS;
    cmsg-&GT; CMSG_LEN = CMSG_LEN(的sizeof(FD));    memmove与(CMSG_DATA(CMSG),放大器; FD,的sizeof(FD));    msg.msg_controllen = cmsg-&GT; CMSG_LEN;    如果(SENDMSG(插座,&放大器;味精,0)℃的)
        err_syserr(无法发送邮件\\ n);
}静态的
INT odbierz(INT插座)//接收来自套接字fd
{
    结构指向msghdr味精= {0};    / *在Mac OS X,则需要在结构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);    如果(recvmsg(插座,&放大器;味精,0)℃的)
        err_syserr(无法接收邮件\\ n);    cmsghdr结构* CMSG = CMSG_FIRSTHDR(安培;味精);    err_remark(关于提取FD \\ n);
    INT的fd;
    memmove与(安培; FD,CMSG_DATA(CMSG)的sizeof(FD));
    err_remark(FD提取%d个\\ N,FD);    返回FD;
}INT主(INT ARGC,字符** argv的)
{
    为const char *文件名=./z7.c;    err_setarg0(的argv [0]);
    err_setlogopts(ERR_PID);
    如果(argc个大于1)
        文件名=的argv [1];
    INT SV [2];
    如果(socketpair(AF_UNIX,SOCK_DGRAM,0,SV)!= 0)
        err_syserr(无法创建Unix域套接字对\\ n);    INT PID =叉();
    如果(PID大于0)//在父
    {
        err_remark(父在工作\\ n);
        关闭(SV [1]);
        INT袜子= SV [0];        INT FD =开放(文件名,O_RDONLY);
        如果(FD℃,)
            err_syserr(无法打开文件%s读取\\ n,文件名);        / *读取一些数据来证明,文件传递偏移* /
        字符缓冲区[32];
        INT为nbytes =读(FD,缓冲器,的sizeof(缓冲液));
        如果(为nbytes大于0)
            err_remark(父阅读:[[%* S]]的\\ n,为nbytes,缓冲区);        wyslij(袜子,FD);        关闭(FD);
        了nanosleep(及(结构的timespec){.tv_sec = 1,.tv_nsec = 5亿},0);
        err_remark(父退出的\\ n);
    }
    //其他儿童
    {
        err_remark(孩子在作怪\\ n);
        关闭(SV [0]);
        INT袜子= SV [1];        了nanosleep(及(结构的timespec){.tv_sec = 0,.tv_nsec = 5亿},0);        INT FD = odbierz(袜子);
        的printf(读%d个\\ N!FD);
        字符缓冲区[256];
        ssiz​​e_t供为nbytes;
        而((为nbytes =读(FD,缓冲器,的sizeof(缓冲液)))0)
            写(1,缓冲,为nbytes);
        的printf(完成\\ n!);
        关闭(FD);
    }
    返回0;
}

和一个样品运行:

$ ./fd-passing
FD-传:PID = 8000:家长工作
FD-传:PID = 8000:家长阅读:[这是文件z7.c.
它不是]
FD-传:PID = 8001:儿童在玩耍
FD-传:PID = 8001:关于提取FD
FD-传:PID = 8001:提取FD 3
阅读3!
很有意思。
它甚至不是C $ C $℃。
但所使用的FD-传递方案证明文件
描述的确可以偶尔插座之间传递。
并且,与完全工作code,它确实似乎工作。
扩展测试将有父code读取文件的一部分,
然后证明孩子codecontinues如果母公司不放过。
尚未codeD,虽然。
完成!
FD-传: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, '\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->cmsg_len;

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

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

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, '\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->cmsg_len;

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

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天全站免登陆