UNIX domain sockets let two processes on the same machine talk to each other. Unlike TCP/IP sockets that go through the network stack, UNIX domain sockets use the file system as the address space โ a socket is identified by a file path like /tmp/mysock. This makes them faster and more secure for local IPC.
In this part you will learn what the socket address structure looks like, how to fill it, and what really happens when you call bind() on a UNIX domain socket.
What is a UNIX Domain Socket?
A socket belongs to a communication domain. The domain decides what kind of addresses the socket uses and how data travels. There are three common domains:
| Domain | Constant | Address Type | Scope |
|---|---|---|---|
| UNIX | AF_UNIX |
File system pathname | Same machine only |
| IPv4 | AF_INET |
IP address + port | Network (any host) |
| IPv6 | AF_INET6 |
IPv6 address + port | Network (any host) |
UNIX domain sockets support both stream (like TCP, connection-oriented) and datagram (like UDP, connectionless) communication styles โ but everything stays on the local machine. Since there is no actual network involved, they are much faster and have zero packet overhead.
The sockaddr_un Structure
Every socket address domain has its own structure. For UNIX domain sockets it is struct sockaddr_un defined in <sys/un.h>:
#include <sys/un.h>
struct sockaddr_un {
sa_family_t sun_family; /* Always AF_UNIX */
char sun_path[108]; /* Null-terminated socket pathname */
};
Two fields โ that is all:
Why is sun_ prefix used? It stands for socket unix โ nothing to do with Sun Microsystems.
Portability note: SUSv3 does not fix the size of sun_path. Different systems use different sizes โ BSD used 108, HP-UX 11 uses only 92. For maximum portability always write to the smaller value (92 bytes) and use snprintf() or strncpy() to prevent buffer overflows.
Binding a UNIX Domain Socket
Binding assigns an address (a file path) to a socket. You fill a sockaddr_un struct and call bind(). Here is the step-by-step process:
socket(AF_UNIX, SOCK_STREAM, 0) to create the socket fdmemset(&addr, 0, sizeof(addr)) โ zero out the entire struct firstaddr.sun_family = AF_UNIXstrncpy(addr.sun_path, SOCKNAME, sizeof(addr.sun_path) - 1)bind(sfd, (struct sockaddr *)&addr, sizeof(addr))Complete working example:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#define SOCKNAME "/tmp/mysock"
int main(void)
{
int sfd;
struct sockaddr_un addr;
/* Step 1: Create UNIX domain stream socket */
sfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sfd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
/* Step 2: Zero out the entire structure.
This also handles any non-standard fields in some implementations */
memset(&addr, 0, sizeof(struct sockaddr_un));
/* Step 3: Set address family */
addr.sun_family = AF_UNIX;
/* Step 4: Copy the socket path.
Use sizeof-1 so sun_path always has a null terminator.
memset already wrote 0s, so the last byte stays null. */
strncpy(addr.sun_path, SOCKNAME, sizeof(addr.sun_path) - 1);
/* Step 5: Bind socket to the pathname */
if (bind(sfd, (struct sockaddr *) &addr,
sizeof(struct sockaddr_un)) == -1) {
perror("bind");
close(sfd);
exit(EXIT_FAILURE);
}
printf("Socket bound to %s\n", SOCKNAME);
/* When done, remove the socket file from the filesystem */
close(sfd);
unlink(SOCKNAME);
return 0;
}
Why use memset() instead of just assigning fields? Some systems add extra non-standard fields to sockaddr_un. If you only assign sun_family and sun_path, those hidden fields hold garbage values. memset() clears everything to zero, making the code safe on all platforms. Note: bzero() does the same job but is marked LEGACY in SUSv3 and removed in SUSv4 โ always use memset() instead.
What Happens in the Filesystem After bind()?
When you call bind() on a UNIX domain socket, Linux creates an actual file system entry at that path. This is different from internet sockets where there is no file created.
ls: cannot access ‘/tmp/mysock’: No such file or directory
srw-r–r– 1 ravi ravi 0 Jun 13 10:00 /tmp/mysock=
When stat() is called on the socket path, the st_mode field contains S_IFSOCK as the file type. Even though the socket looks like a file, you cannot use open() to open it โ you must use socket APIs.
#include <sys/stat.h>
#include <stdio.h>
/* Check if a path is a socket */
int is_socket(const char *path)
{
struct stat sb;
if (stat(path, &sb) == -1)
return 0;
return S_ISSOCK(sb.st_mode); /* Returns 1 if it's a socket */
}
The key point: I/O on UNIX domain sockets does not go to the underlying filesystem device. The path is just a name/address. Data travels through kernel buffers, not through disk.
Important Rules When Binding
If
/tmp/mysock already exists (even as a leftover from a previous run), bind() fails with EADDRINUSE. Always call remove() or unlink() first if the file might exist.A relative path works but is fragile โ the connecting process must be running from the exact same working directory. Always use absolute paths like
/var/run/myapp.sock.A single socket can only be bound to one pathname, and one pathname can only be bound to one socket at a time.
Even though the socket is visible in the filesystem, you cannot open it with
open(). You must use socket-related calls โ connect(), accept(), etc.When the socket is no longer needed, its filesystem entry must be manually removed using
unlink(pathname) or remove(pathname). Closing the socket fd does not delete the file.Practical pattern โ safe server startup:
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#define SV_SOCK_PATH "/tmp/myserver.sock"
int create_server_socket(void)
{
int sfd;
struct sockaddr_un addr;
sfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sfd == -1) { perror("socket"); exit(EXIT_FAILURE); }
/* Remove old socket file if it exists from a previous crash.
If remove() fails for any reason other than "file not found",
that's a real error. */
if (remove(SV_SOCK_PATH) == -1 && errno != ENOENT) {
perror("remove");
exit(EXIT_FAILURE);
}
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SV_SOCK_PATH, sizeof(addr.sun_path) - 1);
if (bind(sfd, (struct sockaddr *) &addr,
sizeof(struct sockaddr_un)) == -1) {
perror("bind");
exit(EXIT_FAILURE);
}
return sfd;
}
Security warning: Binding to /tmp is fine for examples but dangerous in production. Any process can create a file with the same name before your server starts, causing a denial-of-service. Real applications should use a secured directory with appropriate permissions, like /var/run/myapp/.
File Permissions and Socket Access Control
Because a UNIX domain socket appears in the filesystem, standard Linux file permissions apply to it. This gives you a simple but effective access control mechanism:
| Who can connect? | Permissions needed on socket file | How to set |
|---|---|---|
| Owner only | rw------- (0600) |
chmod(path, 0600) |
| Owner + group | rw-rw---- (0660) |
chmod(path, 0660) |
| Everyone | rw-rw-rw- (0666) |
chmod(path, 0666) |
On Linux, the execute permission on the socket file is checked for connect(). The permissions on the directory containing the socket are also important โ a process needs execute permission on the directory to reach the socket.
Interview Questions
A UNIX domain socket is an IPC mechanism where processes on the same machine communicate using a file system path as the address. A TCP socket uses an IP address and port number and goes through the network stack, even for localhost. UNIX domain sockets are faster (no network overhead, no checksums, no packet framing), more secure (filesystem permissions control access), and support passing open file descriptors between processes โ something TCP sockets cannot do.
Two fields: sun_family (always AF_UNIX) and sun_path (a null-terminated character array holding the socket’s file system path, typically 108 bytes). The sun_ prefix stands for socket unix.
Some systems add non-standard extra fields to sockaddr_un. If you only assign sun_family and sun_path, those hidden fields are uninitialized garbage. memset(&addr, 0, sizeof(addr)) zeroes the whole structure, making the code portable and safe on all POSIX implementations.
bind() fails with errno == EADDRINUSE. The fix is to call remove(SV_SOCK_PATH) before bind(), but only ignore the error if errno == ENOENT (file did not exist). Any other error from remove() should be treated as a real problem.
No. Closing the socket file descriptor does not remove the socket file. You must explicitly call unlink(pathname) or remove(pathname) to clean it up. Failing to do so means the stale socket file will block the next bind() call with EADDRINUSE.
No. Even though the socket is visible in the filesystem (as file type s), open() will fail. You must use socket API calls โ connect() on the client side and accept() on the server side.
The /tmp directory is world-writable. An attacker can create a file at /tmp/yourapp.sock before your server starts. When your server tries to bind() to that path, it gets EADDRINUSE and cannot start โ a simple denial-of-service attack. Production servers should use secured directories with restricted permissions, such as /var/run/myapp/.
The st_mode field in the stat structure will have the file type bits set to S_IFSOCK. The convenience macro S_ISSOCK(sb.st_mode) returns true (non-zero) for socket files. In ls -l output, the first character is s.
Part 2 covers Stream Sockets in the UNIX Domain โ building a real client-server with connect(), listen(), accept(), and data transfer.
