420 likes | 1.01k Views
Unix Domain Protocols. Chapter 14. Unix domain protocol. contents. Introduction unix domain socket address structure socketpair socket function unix domain stream client-server unix domain datagram client-server passing descriptors receiving sender credentials. Unix Domain Protocol.
E N D
Chapter 14 Unix domain protocol
contents Introduction unix domain socket address structure socketpair socket function unix domain stream client-server unix domain datagram client-server passing descriptors receiving sender credentials
Unix Domain Protocol perform client-server communication on a single host using same API that is used for client-server model on the different hosts. Faster than internet protocol suite UNIX domain sockets only copy data; they have no protocol processing to perform, no network headers to add or remove, no checksums to calculate, no sequence numbers to generate, and no acknowledgements to send. The Unix domain protocols are an alternative to the interprocess communication (IPC) methods described
Unix Domain Protocol Two types of sockets are provided in the Unix domain: stream sockets (similar to TCP) datagram sockets (similar to UDP). The UNIX domain datagram service is reliable, however. Messages are neither lost nor delivered out of order
Unix Domain Protocol Unix domain sockets are used for three reasons: Unix domain sockets are often twice as fast as a TCP socket when both peers are on the same host used when passing descriptors between processes on the same host. Unix domain sockets provide the client's credentials (user ID and group IDs) to the server, which can provide additional security checking
Unix Domain Protocol End Point Address pathnames within the normal filesystem The pathname associated with a Unix domain socket should be an absolute pathname
unix domain socket address structure <sys/un.h> struct sockaddr_un{ uint8_t sun_len; sa_family_t sun_family; /*AF_LOCAL*/ char sun_path[104]; /*null terminated pathname*/ }; sun_path => must null terminated
socketpair Function Create two sockets that are then connected together(only available in unix domain socket) family must be AF_LOCAL protocol must be 0 #include<sys/socket.h> int socketpair(int family, int type, int protocol, int sockfd[2]); return: nonzero if OK, -1 on error
socketpair Function Although the socketpair function creates sockets that are connected to each other, the individual sockets don't have names. This means that they can't be addressed by unrelated processes.
unix domain stream client-server #include "unp.h" int main(int argc, char **argv) { int listenfd, connfd; pid_t childpid; socklen_t clilen; struct sockaddr_un cliaddr, servaddr; void sig_chld(int); listenfd = Socket(AF_LOCAL, SOCK_STREAM, 0); unlink(UNIXSTR_PATH); bzero(&servaddr, sizeof(servaddr)); servaddr.sun_family = AF_LOCAL; strcpy(servaddr.sun_path, UNIXSTR_PATH); Bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); Listen(listenfd, LISTENQ); Signal(SIGCHLD, sig_chld);
unix domain stream client-server(2) for ( ; ; ) { clilen = sizeof(cliaddr); if ( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0) { if (errno == EINTR) continue; /* back to for() */ else err_sys("accept error"); } if ( (childpid = Fork()) == 0) { /* child process */ Close(listenfd); /* close listening socket */ str_echo(connfd); /* process the request */ exit(0); } Close(connfd); /* parent closes connected socket */ } }
passing descriptors Current unix system provide a way to pass any open descriptor from one process to any other process.(using sendmsg) The ability to pass an open file descriptor between processes is powerful. It can lead to different ways of designing clientserver applications. It allows one process (typically a server) to do everything that is required to open a file (involving such details as translating a network name to a network address, dialing a modem, negotiating locks for the file, etc.) and simply pass back to the calling process a descriptor that can be used with all the I/O functions. All the details involved in opening the file or device are hidden from the client.
passing descriptors(2) Create a unix domain socket(stream or datagram) one process opens a descriptor by calling any of the unix function that returns a descriptor the sending process build a msghdr structure containing the descriptor to be passed the receiving process calls recvmsg to receive the descriptor on the unix domain socket Passing a descriptor is not passing a descriptor number, but involves creating a new descriptor in the receiving process that refers to the same file table entry within the kernel as the descriptor that was sent by the sending process.
Descriptor passing example [0] [1] After creating stream pipe using socketpair
fork mycat openfile [0] [1] Exec(command-line args) descriptor mycat program after invoking openfile program
recvmsg and sendmsg #include <sys/socket.h> ssize_t recvmsg (int sockfd, struct msghdr *msg, int flags); ssize_t sendmsg (int sockfd, struct msghdr *msg, int flags); Struct msghdr { void *msg_name; /* starting address of buffer */ socklen_t msg_namelen; /* size of protocol address */ struct iovec *msg_iov; /* scatter/gather array */ size_t msg_iovlen; /* # elements in msg_iov */ void *msg_control; /* ancillary data; must be aligned for a cmsghdr structure */ socklen_t msg_controllen; /* length of ancillary data */ int msg_flags; /* flags returned by recvmsg() */ };
Ancillary Data Ancillary data can be sent and received using the msg_control and msg_controllen members of the msghdr structure with sendmsg and recvmsg functions.
Control Message Header struct cmsghdr { socklen_t cmsg_len; /* data byte count, including header */ int cmsg_level; /* originating protocol */ int cmsg_type; /* protocol-specific type */ /* followed by the actual control message data */ };
Control Message Header To send a file descriptor, set cmsg_len to the size of the cmsghdr structure, plus the size of an integer (the descriptor). The cmsg_level field is set to SOL_SOCKET, and cmsg_type is set to SCM_RIGHTS, to indicate that we are passing access rights. (SCM stands for socket-level control message.) Access rights can be passed only across a UNIX domain socket. The descriptor is stored right after the cmsg_type field, using the macro CMSG_DATA to obtain the pointer to this integer.
Control Message Header #include <sys/socket.h> /* size of control buffer to send/recv one file descriptor */ #define CONTROLLEN CMSG_LEN(sizeof(int)) static struct cmsghdr *cmptr = NULL; /* malloc'ed first time */ /* * Pass a file descriptor to another process. * If fd<0, then -fd is sent back instead as the error status. */ int send_fd(int fd, int fd_to_send) { struct iovec iov[1]; struct msghdr msg; char buf[2]; /* send_fd()/recv_fd() 2-byte protocol */ iov[0].iov_base = buf; iov[0].iov_len = 2; msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_name = NULL; msg.msg_namelen = 0; if (fd_to_send < 0) { msg.msg_control = NULL; msg.msg_controllen = 0; buf[1] = -fd_to_send; /* nonzero status means error */ if (buf[1] == 0) buf[1] = 1; } else { if (cmptr == NULL && (cmptr = malloc(CONTROLLEN)) == NULL) return(-1); cmptr->cmsg_level = SOL_SOCKET; cmptr->cmsg_type = SCM_RIGHTS; cmptr->cmsg_len = CONTROLLEN; msg.msg_control = cmptr; msg.msg_controllen = CONTROLLEN; *(int *)CMSG_DATA(cmptr) = fd_to_send; /* the fd to pass */ buf[1] = 0; /* zero status means OK */ } buf[0] = 0; /* null byte flag to recv_fd() */ if (sendmsg(fd, &msg, 0) != 2) return(-1); return(0); }
Control Message Header #include "apue.h" #include <sys/socket.h> /* struct msghdr */ /* size of control buffer to send/recv one file descriptor */ #define CONTROLLEN CMSG_LEN(sizeof(int)) static struct cmsghdr *cmptr = NULL; /* malloc'ed first time */ /* * Receive a file descriptor from a server process. Also, any data * received is passed to (*userfunc)(STDERR_FILENO, buf, nbytes). * We have a 2-byte protocol for receiving the fd from send_fd(). */ int recv_fd(int fd, ssize_t (*userfunc)(int, const void *, size_t)) { int newfd, nr, status; char *ptr; char buf[MAXLINE]; struct iovec iov[1]; struct msghdr msg; status = -1; for ( ; ; ) { iov[0].iov_base = buf; iov[0].iov_len = sizeof(buf); msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_name = NULL; msg.msg_namelen = 0; if (cmptr == NULL && (cmptr = malloc(CONTROLLEN)) == NULL) return(-1); if (cmptr == NULL && (cmptr = malloc(CONTROLLEN)) == NULL) return(-1); msg.msg_control = cmptr; msg.msg_controllen = CONTROLLEN; if ((nr = recvmsg(fd, &msg, 0)) < 0) { err_sys("recvmsg error"); } else if (nr == 0) { err_ret("connection closed by server"); return(-1); } for (ptr = buf; ptr < &buf[nr]; ) { if (*ptr++ == 0) { if (ptr != &buf[nr-1]) err_dump("message format error"); status = *ptr & 0xFF; /* prevent sign extension */ if (status == 0) { if (msg.msg_controllen != CONTROLLEN) err_dump("status = 0 but no fd"); newfd = *(int *)CMSG_DATA(cmptr); } else { newfd = -status; } nr -= 2; } } if (nr > 0 && (*userfunc)(STDERR_FILENO, buf, nr) != nr) return(-1); if (status >= 0) /* final data has arrived */ return(newfd); /* descriptor, or -status */ } }
Control Message Header if (cmptr == NULL && (cmptr = malloc(CONTROLLEN)) == NULL) return(-1); msg.msg_control = cmptr; msg.msg_controllen = CONTROLLEN; if ((nr = recvmsg(fd, &msg, 0)) < 0) { err_sys("recvmsg error"); } else if (nr == 0) { err_ret("connection closed by server"); return(-1); } for (ptr = buf; ptr < &buf[nr]; ) { if (*ptr++ == 0) { if (ptr != &buf[nr-1]) err_dump("message format error"); status = *ptr & 0xFF; /* prevent sign extension */ if (status == 0) { if (msg.msg_controllen != CONTROLLEN) err_dump("status = 0 but no fd"); newfd = *(int *)CMSG_DATA(cmptr); } else { newfd = -status; } nr -= 2; } } if (nr > 0 && (*userfunc)(STDERR_FILENO, buf, nr) != nr) return(-1); if (status >= 0) /* final data has arrived */ return(newfd); /* descriptor, or -status */ } }
#include "unp.h" int my_open(const char *, int); int main(int argc, char **argv) { int fd, n; char buff[BUFFSIZE]; if (argc != 2) err_quit("usage: mycat <pathname>"); if ( (fd = my_open(argv[1], O_RDONLY)) < 0) err_sys("cannot open %s", argv[1]); while ( (n = Read(fd, buff, BUFFSIZE)) > 0) Write(STDOUT_FILENO, buff, n); exit(0); } mycat program show in Figure 14.7)
#include "unp.h" int my_open(const char *pathname, int mode) { int fd, sockfd[2], status; pid_t childpid; char c, argsockfd[10], argmode[10]; Socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd); if ( (childpid = Fork()) == 0) { /* child process */ Close(sockfd[0]); snprintf(argsockfd, sizeof(argsockfd), "%d", sockfd[1]); snprintf(argmode, sizeof(argmode), "%d", mode); execl("./openfile", "openfile", argsockfd, pathname, argmode, (char *) NULL); err_sys("execl error"); } myopen function(1) : open a file and return a descriptor
/* parent process - wait for the child to terminate */ Close(sockfd[1]); /* close the end we don't use */ Waitpid(childpid, &status, 0); if (WIFEXITED(status) == 0) err_quit("child did not terminate"); if ( (status = WEXITSTATUS(status)) == 0) Read_fd(sockfd[0], &c, 1, &fd); else { errno = status; /* set errno value from child's status */ fd = -1; } Close(sockfd[0]); return(fd); } myopen function(2) : open a file and return a descriptor
receiving sender credentials User credentials via fcred structure Struct fcred{ uid_t fc_ruid; /*real user ID*/ gid_t fc_rgid; /*real group ID*/ char fc_login[MAXLOGNAME];/*setlogin() name*/ uid_t fc_uid; /*effectivr user ID*/ short fc_ngroups; /*number of groups*/ gid_t fc_groups[NGROUPS]; /*supplemenary group IDs*/ }; #define fc_gid fc_groups[0] /* effective group ID */
receiving sender credentials(2) Usally MAXLOGNAME is 16 NGROUP is 16 fc_ngroups is at least 1 the credentials are sent as ancillary data when data is sent on unix domain socket.(only if receiver of data has enabled the LOCAL_CREDS socket option) on a datagram socket , the credentials accompany every datagram. Credentials cannot be sent along with a descriptor user are not able to forge credentials