This chapter looks at the use of UNIX domain sockets, which allow communication between processes on the same host system. We discuss the use of both stream and datagram sockets in the UNIX domain. We also describe the use of file permissions to control access to UNIX domain sockets, the use of socketpair() to create a pair of connected UNIX domain sockets, and the Linux abstract socket namespace.
In the UNIX domain, a socket address takes the form of a pathname, and the domain-specific socket address structure is defined as follows:
struct sockaddr_un { sa_family_t sun_family; /* Always AF_UNIX */ char sun_path[108]; /* Null-terminated socket pathname */ };
The prefix sun_ in the fields of the sockaddr_un structure has nothing to do with Sun Microsystems; rather, it derives from socket unix.
SUSv3 doesn’t specify the size of the sun_path field. Early BSD implementations used 108 and 104 bytes, and one contemporary implementation (HP-UX 11) uses 92 bytes. Portable applications should code to this lower value, and use snprintf() or strncpy() to avoid buffer overruns when writing into this field.
In order to bind a UNIX domain socket to an address, we initialize a sockaddr_un structure, and then pass a (cast) pointer to this structure as the addr argument to bind(), and specify addrlen as the size of the structure, as shown in Example 57-1.
Example 57-1. Binding a UNIX domain socket
const char *SOCKNAME = "/tmp/mysock"; int sfd; struct sockaddr_un addr; sfd = socket(AF_UNIX, SOCK_STREAM, 0); /* Create socket */ if (sfd == -1) errExit("socket"); memset(&addr, 0, sizeof(struct sockaddr_un)); /* Clear structure */ addr.sun_family = AF_UNIX; /* UNIX domain address */ strncpy(addr.sun_path, SOCKNAME, sizeof(addr.sun_path) - 1); if (bind(sfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) == -1) errExit("bind");
The use of the memset() call in Example 57-1 ensures that all of the structure fields have the value 0. (The subsequent strncpy() call takes advantage of this by specifying its final argument as one less than the size of the sun_path field, to ensure that this field always has a terminating null byte.) Using memset() to zero out the entire structure, rather than initializing individual fields, ensures that any nonstandard fields that are provided by some implementations are also initialized to 0.
The BSD-derived function bzero() is an alternative to memset() for zeroing the contents of a structure. SUSv3 specifies bzero() and the related bcopy() (which is similar to memmove()), but marks both functions LEGACY, noting that memset() and memmove() are preferred. SUSv4 removes the specifications of bzero() and bcopy().
When used to bind a UNIX domain socket, bind() creates an entry in the file system. (Thus, a directory specified as part of the socket pathname needs to be accessible and writable.) The ownership of the file is determined according to the usual rules for file creation (Ownership of New Files). The file is marked as a socket. When stat() is applied to this pathname, it returns the value S_IFSOCK
in the file-type component of the st_mode field of the stat structure (Retrieving File Information: stat()). When listed with ls -l, a UNIX domain socket is shown with the type s in the first column, and ls -F appends an equal sign (=) to the socket pathname.
Although UNIX domain sockets are identified by pathnames, I/O on these sockets doesn’t involve operations on the underlying device.
The following points are worth noting about binding a UNIX domain socket:
We can’t bind a socket to an existing pathname (bind() fails with the error EADDRINUSE
).
It is usual to bind a socket to an absolute pathname, so that the socket resides at a fixed address in the file system. Using a relative pathname is possible, but unusual, because it requires an application that wants to connect() to this socket to know the current working directory of the application that performs the bind().
A socket may be bound to only one pathname; conversely, a pathname can be bound to only one socket.
We can’t use open() to open a socket.
When the socket is no longer required, its pathname entry can (and generally should) be removed using unlink() (or remove()).
In most of our example programs, we bind UNIX domain sockets to pathnames in the /tmp
directory, because this directory is normally present and writable on every system. This makes it easy for the reader to run these programs without needing to first edit the socket pathnames. Be aware, however, that this is generally not a good design technique. As pointed out in Pitfalls When Performing File Operations and File I/O, creating files in publicly writable directories such as /tmp
can lead to various security vulnerabilities. For example, by creating a pathname in /tmp
with the same name as that used by the application socket, we can create a simple denial-of-service attack. Real-world applications should bind() UNIX domain sockets to absolute pathnames in suitably secured directories.