260 likes | 456 Views
Unix Domain Sockets. Computer Network Programming. Outline. Motivation What are Unix domain sockets address structure How to use unix domain sockets Example client-server apps using UDSs. Example use of UDSs. Passing descriptors Passing credentials. Motivation.
E N D
Unix Domain Sockets Computer Network Programming
Outline • Motivation • What are Unix domain sockets • address structure • How to use unix domain sockets • Example client-server apps using UDSs. • Example use of UDSs. • Passing descriptors • Passing credentials
Motivation • We need a way of interprocess communication between process running in the same host • faster than TCP/IP • But we want to use the same socket API • hence our programs for TCP and UDP sockets will work with little modifications for processes running on the same host. • (TCP and UDP sockets can also be used for communicatşon between process on the same host but it is slowerUse Unix Domain Sockets
Unix Domain Sockets • Not an actual protocol suite like TCP/IP • For Unix only • A way of performing IPC between processes running on the same host using the same socket API. • Two types of UD sockets • stream sockets (similar to TCP) • datagram sockets (similar to UDP)
Uses of UD sockets • They are faster than TCP and UDP sockets • at least twice • for example X Window System uses UD sockets of client is on the same host as the server • client checks the DISPLAY environment variable • Used for passing file (socket) descriptors • any descriptor can be passed • Used to pass client’s credentials (user ID etc) to the server (can be used for security check).
Protocol Addresses • Pathnames are used as protocol addresses • instead of IP address, port number pairstruct sockaddr_un { uint8_t sun_len;/* 1 byte */ sa_familiy_t sun_family;/* 1 byte */ char sun_path[104];} • The pathnames must be null-terminated. • If pathname is null-string, it corresponds to INADDR_ANY constant.
How to bind a protocol address (pathname) to a UD socket int main(int argc, char **argv) { int sockfd; socklen_t len; struct sockaddr_un addr1, addr2; if (argc != 2) err_quit("usage: unixbind <pathname>"); sockfd = Socket(AF_LOCAL, SOCK_STREAM, 0); unlink(argv[1]); /* OK if this fails */ bzero(&addr1, sizeof(addr1)); addr1.sun_family = AF_LOCAL; strncpy(addr1.sun_path, argv[1], sizeof(addr1.sun_path)-1); Bind(sockfd, (SA *) &addr1, SUN_LEN(&addr1)); len = sizeof(addr2); Getsockname(sockfd, (SA *) &addr2, &len); printf("bound name = %s, returned len = %d\n", addr2.sun_path, len); exit(0); } If pathname exists already, bind will fail. Therefore we remove the pathname first using unlink function When bind() is called it creates a file(path) corresponding to the pathname (protocol address) provided as argument
Notes about use Socket Function with UD sockets • The pathname created should have file permissions as 0777 • can read, write and execute by user, group or others • The pathname should be absolute pathname. • so that client and server does not have to be run in the same directory • For a client to be able to issue a connect() • there has to be pathname in the file system • there has to be an open socket on that pathname • the socket and pathname should be of the same type • stream or datagram
Notes about use Socket Function with UD sockets • Unix domain stream sockets are similar to TCP sockets: byte stream oriented, no record boundaries • Unix domain datagram sockets are similar to UDP sockets: unreliable and preserves record boundaries. • Unlike UDP, sending a datagram out of a socket does not bind a pathname to the socket. • Hence receiver can not send a reply back unless the sender binds a pathname to its UD socket. • Similarly issuing connect does not bind a pathname to the socket
Sockpair function • This function creates two sockets that are then connected together. (only for Unix domain sockets)int socketpair(int family, int type, int protocol, int sockfd[2]); • family is AF_LOCAL • protocol is 0 • type is SOCK_DGRAM or SOCK_STREAM • sockfd[0] and sockfd[1] are two socket descriptors • created sockets are unnamed • they are full-duplex (data can go in each direction) • also called stream pipes if type is SOCK_STREAM
Example of Unix Domain Sockets: StreamServer #define UNIXSTR_PATH "/tmp/unix.str" 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); for ( ; ; ) { /* Rest is same with a TCP server: accept() called fork() called child process serves the requests*/ } }
Example of Unix Domain Sockets: StreamClient #define UNIXSTR_PATH "/tmp/unix.str” int main(int argc, char **argv) { int sockfd; struct sockaddr_un servaddr; sockfd = Socket(AF_LOCAL, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sun_family = AF_LOCAL; strcpy(servaddr.sun_path, UNIXSTR_PATH); Connect(sockfd, (SA *) &servaddr, sizeof(servaddr)); str_cli(stdin, sockfd); /* do it all */ exit(0); } Same with a TCP client except we need to fill a sockaddr_un structure with the information about the server protocol address
Example of Unix Domain Sockets: Datagram Server #define UNIXDG_PATH "/tmp/unix.dg" int main(int argc, char **argv) { int sockfd; struct sockaddr_un servaddr, cliaddr; sockfd = Socket(AF_LOCAL, SOCK_DGRAM, 0); unlink(UNIXDG_PATH); bzero(&servaddr, sizeof(servaddr)); servaddr.sun_family = AF_LOCAL; strcpy(servaddr.sun_path, UNIXDG_PATH); Bind(sockfd, (SA *) &servaddr, sizeof(servaddr)); dg_echo(sockfd, (SA *) &cliaddr, sizeof(cliaddr)); } Same with a UDP server except we need to fill a sockaddr_un structure with the information about the server protocol address and bind it to the server socket
Example of Unix Domain Sockets: DatagramClient Similar to a UDP client except we need to bind a protocol address to the socket tmpnam() generates a temporary file name. #define UNIXDG_PATH "/tmp/unix.dg" int main(int argc, char **argv) { int sockfd; struct sockaddr_un cliaddr, servaddr; sockfd = Socket(AF_LOCAL, SOCK_DGRAM, 0); bzero(&cliaddr, sizeof(cliaddr)); /* bind an address for us */ cliaddr.sun_family = AF_LOCAL; strcpy(cliaddr.sun_path, tmpnam(NULL)); Bind(sockfd, (SA *) &cliaddr, sizeof(cliaddr)); bzero(&servaddr, sizeof(servaddr)); /* fill in server's address */ servaddr.sun_family = AF_LOCAL; strcpy(servaddr.sun_path, UNIXDG_PATH); dg_cli(stdin, sockfd, (SA *) &servaddr, sizeof(servaddr)); exit(0); }
Passing Descriptors • A descriptor is an integer value that corresponds to a socket or file or terminal device etc. • We learned how to pass open descriptors from patent to child. • Similarly we can pass descriptors as the arguments of exec while executing an other program from the child process • Files and sockets corresponding to descriptors that are passed from parent to child remain open. • We want also pass descriptors: • from a child to a parent • between two unrelated processes • We can achieve this by using Unix domain sockets
Steps involved in passing descriptors between two processes • Create a Unix domain socket (stream or datagram) • if the processes are parent and child then we can use socketpair function • if processes are unrelated then one should be server and bind a well known pathname to the socket and the client should talk/connect to this socket. • One process (sending process) • open a descriptor • for example open a file (using open, socket, accept, pipe…) • builds a msghdr structure containg the descriptor to be passed and then calls sendmsg • Receiving process calls recvmsg to obtain the descriptor and then uses it.
Example: mycat program We want to execute the following scenerio openfile program mycat program Unix domain stream communication (2) child parent (4) fd stdout (screen) fd fd (1) (3) (1) Child open a file and obtains descriptor fd (2) Child sends the descriptor to parent (3) Parent reads from the received descriptor - namely reads from the file (4) Parent prints the content of the file to the screen file By this method, a process may be executing as root and opening important files for other clients.
mycat program fist create a socket pair (stream pipe) mycat [0] [1] then fork and exec a new program exit (exit status) mycat openfile fork exec(pathname, mode, sockfd) [0] [1] descriptor
Mycat program 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); }
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"); } /* 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); }
Definition of msghdr structure in /usr/include/sys/socket.h /* * Message header for recvmsg and sendmsg calls. */ #if !defined(_XPG4_2) struct msghdr { caddr_t msg_name; /* optional address */ int msg_namelen; /* size of address */ struct iovec *msg_iov; /* scatter/gather array */ int msg_iovlen; /* # elements in msg_iov */ caddr_t msg_accrights; /* access rights sent/received */ int msg_accrightslen; }; #else struct msghdr { void *msg_name; /* optional address */ size_t msg_namelen; /* size of address */ struct iovec *msg_iov; /* scatter/gather array */ int msg_iovlen; /* # elements in msg_iov */ void *msg_control; /* ancillary data */ size_t msg_controllen; /* ancillary data buffer len */ int msg_flags; /* flags on received message */ }; #endif /* !defined(_XPG4_2) */
read_fd() function ssize_t read_fd(int fd, void *ptr, size_t nbytes, int *recvfd) { struct msghdr msg; struct iovec iov[1]; ssize_t n; int newfd; #ifdef HAVE_MSGHDR_MSG_CONTROL union { struct cmsghdr cm; char control[CMSG_SPACE(sizeof(int))]; } control_un; struct cmsghdr *cmptr; msg.msg_control = control_un.control; msg.msg_controllen = sizeof(control_un.control); #else msg.msg_accrights = (caddr_t) &newfd; msg.msg_accrightslen = sizeof(int); #endif msg.msg_name = NULL; msg.msg_namelen = 0; iov[0].iov_base = ptr; iov[0].iov_len = nbytes; msg.msg_iov = iov; msg.msg_iovlen = 1; if ( (n = recvmsg(fd, &msg, 0)) <= 0) return(n); /* continued in the next page */
read_fd() function continued /* conitinued from previous page */ #ifdef HAVE_MSGHDR_MSG_CONTROL if ( (cmptr = CMSG_FIRSTHDR(&msg)) != NULL && cmptr->cmsg_len == CMSG_LEN(sizeof(int))) { if (cmptr->cmsg_level != SOL_SOCKET) err_quit("control level != SOL_SOCKET"); if (cmptr->cmsg_type != SCM_RIGHTS) err_quit("control type != SCM_RIGHTS"); *recvfd = *((int *) CMSG_DATA(cmptr)); } else *recvfd = -1; /* descriptor was not passed */ #else if (msg.msg_accrightslen == sizeof(int)) *recvfd = newfd; else *recvfd = -1; /* descriptor was not passed */ #endif return(n); }
openfile program int main(int argc, char **argv) { int fd; ssize_t n; if (argc != 4) err_quit("openfile <sockfd#> <filename> <mode>"); if ( (fd = open(argv[2], atoi(argv[3]))) < 0) exit( (errno > 0) ? errno : 255 ); if ( (n = write_fd(atoi(argv[1]), "", 1, fd)) < 0) exit( (errno > 0) ? errno : 255 ); exit(0); } write_fd similar to read_fd. It prepares a msghdr structure with control data (descriptor) in it and sends it to the mycat program using sendmsg() function over Unix Datagram Stream Socket.
Sending Credentials • Similar to sending descriptors, user credentials can be sent over a Unix datagram socket using sendmsg() and recvmsg() again as control (ancillary) data • credentials include: • read user ID (from password file for user) • read group ID (from password file for user) • effective user ID • login name • supplementary group id’s etc.