c linux sockets file-descriptor

Enviar descriptor de archivo por socket Linux



sockets file-descriptor (1)

Estoy tratando de enviar un descriptor de archivo por linux socket, pero no funciona. ¿Qué estoy haciendo mal? ¿Cómo se supone que uno debe depurar algo como esto? Intenté poner perror () en todas partes es posible, pero dijeron que todo está bien. Aquí está lo que he escrito:

#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) Programación de redes UNIX®, Vol. 1: La API de Sockets Networking describe el proceso de transferencia de descriptores de archivos entre procesos en el Capítulo 15 Protocolos de dominio Unix y específicamente §15.7 Descriptores de paso . Es complicado describirlo en su totalidad, pero debe hacerse en un socket de dominio Unix ( AF_UNIX o AF_LOCAL ), y el proceso del remitente usa sendmsg() mientras que el receptor usa recvmsg() .

Obtuve esta versión ligeramente modificada (e instrumentada) del código de la pregunta para que funcione en Mac OS X 10.10.1 Yosemite con 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_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; }

El resultado de la versión instrumentada pero no fija del código original fue:

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

Tenga en cuenta que el padre finalizó antes que el hijo, por lo que el mensaje apareció en el medio de la salida.

El resultado del código ''fijo'' fue:

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

Los principales cambios significativos fueron agregar la struct iovec a los datos en la struct msghdr en ambas funciones y proporcionar espacio en la función de recepción ( odbierz() ) para el mensaje de control. struct iovec un paso intermedio en la depuración donde proporcioné la struct iovec al padre y se eliminó el error "mensaje demasiado largo" del padre. Para demostrar que estaba funcionando (se pasó un descriptor de archivo), agregué código para leer e imprimir el archivo desde el descriptor de archivo pasado. El código original tenía sleep(0.5) pero dado que sleep() toma un entero sin signo, esto era equivalente a no dormir. Usé literales compuestos C99 para que el niño durmiera durante 0.5 segundos. El padre duerme durante 1,5 segundos para que la salida del niño se complete antes de que el padre salga. También podía usar wait() o waitpid() , pero era demasiado vago para hacerlo.

No he vuelto y comprobé que todas las adiciones fueran necesarias.

El "stderr.h" declara las funciones err_*() . Es el código que escribí (primera versión antes de 1987) para informar errores de manera sucinta. La err_setlogopts(ERR_PID) antepone todos los mensajes con el PID. Para las marcas de tiempo también, err_setlogopts(ERR_PID|ERR_STAMP) haría el trabajo.

Problemas de alineación

Nominal Animal sugiere en un comment :

¿Puedo sugerirle que modifique el código para copiar el descriptor int usando memcpy() lugar de acceder a los datos directamente? No está necesariamente alineado correctamente, razón por la cual el ejemplo de la página de manual también usa memcpy() , y hay muchas arquitecturas de Linux en las que el acceso int no alineado causa problemas (hasta la señal SIGBUS mata el proceso).

Y no solo las arquitecturas de Linux: tanto SPARC como Power requieren datos alineados y, a menudo, ejecutan Solaris y AIX respectivamente. Érase una vez, DEC Alpha también lo requería, pero rara vez se los ve en el campo en estos días.

El código en la página del manual cmsg(3) relacionado con esto es:

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

La asignación a fdptr parece suponer que CMSG_DATA(cmsg) está lo suficientemente bien alineado para convertirse a int * y memcpy() se supone que NUM_FD no es solo 1. Dicho esto, se supone que apunta en la matriz buf , y eso podría no estar lo suficientemente bien alineado como lo sugiere Nominal Animal, por lo que me parece que fdptr es solo un intruso y sería mejor si el ejemplo utilizado:

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

Y el proceso inverso en el extremo receptor sería apropiado. Este programa solo pasa un descriptor de archivo único, por lo que el código se puede modificar para:

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

También parece recordar problemas históricos en varios sistemas operativos con datos auxiliares sin datos normales de carga útil, evitado al enviar también al menos un byte ficticio, pero no puedo encontrar ninguna referencia para verificar, por lo que puedo recordar mal.

Dado que Mac OS X (que tiene una base Darwin / BSD) requiere al menos una struct iovec , incluso si eso describe un mensaje de longitud cero, estoy dispuesto a creer que el código que se muestra arriba, que incluye un mensaje de 3 bytes , es un buen paso en la dirección general correcta. El mensaje tal vez debería ser un solo byte nulo en lugar de 3 letras.

Revisé el código para leer como se muestra a continuación. Utiliza memmove() para copiar el descriptor de archivo memmove() desde el búfer cmsg . Transfiere un solo byte de mensaje, que es un byte nulo.

También tiene el proceso primario leído (hasta) 32 bytes del archivo antes de pasar el descriptor de archivo al secundario. El niño continúa leyendo donde lo dejó el padre. Esto demuestra que el descriptor de archivo transferido incluye el desplazamiento del archivo.

El receptor debe hacer más validación en el cmsg antes de tratarlo como un descriptor de archivo que pasa un mensaje.

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

Y una muestra de ejecución:

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