使用共享内存在两个进程之间传递套接字描述符

Passing socket descriptor between two processes using shared memory

我试图通过两个进程和 Linux 内核之间的共享内存区域在两个进程之间传递套接字描述符,我的目标是用一个进程打开一个 TCP 套接字并使用第二个过程。如果两个进程之间没有套接字管道,这可能吗?

套接字描述符是一个int,如何获取Linux中描述符的原生形式?如果我只是将描述符的本机形式从进程传递到其他进程,我可以将数据写入打开的 TCP 套接字。

使用 unix 套接字发送文件描述符

static int
send_file_descriptor(
  int socket, /* Socket through which the file descriptor is passed */
  int fd_to_send) /* File descriptor to be passed, could be another socket */
{
 struct msghdr message;
 struct iovec iov[1];
 struct cmsghdr *control_message = NULL;
 char ctrl_buf[CMSG_SPACE(sizeof(int))];
 char data[1];

 memset(&message, 0, sizeof(struct msghdr));
 memset(ctrl_buf, 0, CMSG_SPACE(sizeof(int)));

 /* We are passing at least one byte of data so that recvmsg() will not return 0 */
 data[0] = ' ';
 iov[0].iov_base = data;
 iov[0].iov_len = sizeof(data);

 message.msg_name = NULL;
 message.msg_namelen = 0;
 message.msg_iov = iov;
 message.msg_iovlen = 1;
 message.msg_controllen =  CMSG_SPACE(sizeof(int));
 message.msg_control = ctrl_buf;

 control_message = CMSG_FIRSTHDR(&message);
 control_message->cmsg_level = SOL_SOCKET;
 control_message->cmsg_type = SCM_RIGHTS;
 control_message->cmsg_len = CMSG_LEN(sizeof(int));

 *((int *) CMSG_DATA(control_message)) = fd_to_send;

 return sendmsg(socket, &message, 0);
}

我的意思是将完整的 "message" 信息放入共享内存

加上之前的回答,必须在两个进程之间建立套接字对

pid_t pid;
int streamfd[2];
int n;
int conn_fd;
char buffer; // dummy byte character received from parent

if (socketpair(AF_UNIX, SOCK_STREAM, 0, streamfd) < 0) {
        return -1;
}

然后,一个进程必须发送描述符而另一个进程必须接收它。这是接收端(这段代码改编自: https://www.amazon.ca/UNIX-Network-Programming-Richard-Stevens/dp/0139498761 )

int receiveDescriptor(int streamPipe, int* descriptor, void* pBuffer, size_t bytes) {

  struct msghdr msgInstance;
  struct iovec iov[1];
  int n;

  // ancillary (control) data
  // This is where the descriptor will be held

  #ifdef MSGHDR_MSG_CONTROL

  union {
    struct cmsghdr cm;
    char control[CMSG_SPACE(sizeof(int))];
  } control_un;

  struct cmsghdr* cmptr;

  msgInstance.msg_control = control_un.control;
  msgInstance.msg_controllen = sizeof(control_un.control);

#else

  int receivedFD;
  msgInstance.msg_accrights = (caddr_t)&receivedFD;
  msgInstance.msg_accrightslen = sizeof(int);

#endif

  msgInstance.msg_name = NULL;
  msgInstance.msg_namelen = 0;

  iov[0].iov_base = pBuffer;
  iov[0].iov_len = bytes;

  msgInstance.msg_iov = iov;
  msgInstance.msg_iovlen = 1;

  n = recvmsg(streamPipe, &msgInstance, 0);

  if ( n <= 0) {
    return n;
  }

  // assume descriptor will not be received

  *descriptor = -1;

  // get the descriptor

#ifdef MSGHDR_MSG_CONTROL

  if ( (cmptr = CMSG_FIRSTHDR(&msgInstance)) != NULL) {

    if (cmptr->cmsg_len == CMSG_LEN(sizeof(int))) {

        if (cmptr->cmsg_level == SOL_SOCKET && cmptr->cmsg_type == SCM_RIGHTS) {

            *descriptor = *((int*)CMSG_DATA(cmptr));

        }
    }
  }

#else

  if (msgInstance.msg_accrightslen == sizeof(int)) {

    *descriptor = receivedFD
  }

#endif

  return n;
}

不,您不能仅使用某种替代方法来传输本应进入 sendmsg 调用的相同 "stuff"。当您 "pass a file descriptor" 时,您真正传输的是 访问内核内部文件对象 .

cmsg 结构只是一种格式化对内核的请求的方式,您在其中说 "I want to duplicate this open file object, and allow the process that reads this socket to gain access to it"。名称 SCM_RIGHTS 表明您要传输的内容本质上是 权限 .

由于请求是为了操纵具有安全隐患的内核内部对象,您不能偷偷摸摸地绕过它。你必须进行系统调用。 sendmsg 就是它。 (还有其他 fd-passing APIs……我想是 SysV 上的 Streams。我不知道那个在最近的操作系统中是否还存在。至少对于 BSD 和 Linux,sendmsgSCM_RIGHTS 是要走的路。)

一般来说,这正是 msg 和 cmsg 之间的区别:cmsg 用于内核所做的不仅仅是将一些字节从套接字的一端复制到另一端的操作。