UNIX Domain vs Internet Domain Sockets
UNIX domain sockets (AF_UNIX) work just like Internet domain sockets (AF_INET) at the API level — same socket(), bind(), connect(), accept(), read(), write(). The key differences are:
| Property | UNIX Domain (AF_UNIX) | Internet Domain (AF_INET) |
|---|---|---|
| Address | Filesystem path (e.g. /tmp/mysock) | IP + port (e.g. 127.0.0.1:8080) |
| Scope | Same machine only | Any machine on network |
| Speed | Faster (no TCP/IP stack) | Slightly slower (TCP/IP overhead) |
| Security | Filesystem permissions + credentials | Network-level, no UID/GID |
| Cleanup needed | Yes — unlink() the socket file | No — port is released automatically |
The Address Structure: sockaddr_un
Where Internet sockets use struct sockaddr_in (with IP + port), UNIX domain sockets use struct sockaddr_un (with a filesystem path).
#include <sys/un.h>
struct sockaddr_un {
sa_family_t sun_family; /* Always AF_UNIX */
char sun_path[108]; /* Socket file path (null-terminated) */
};
/* --- Comparing the two address types --- */
/* Internet domain: */
struct sockaddr_in inet_addr;
inet_addr.sin_family = AF_INET;
inet_addr.sin_port = htons(8080);
inet_addr.sin_addr.s_addr = htonl(INADDR_ANY);
/* UNIX domain: */
struct sockaddr_un unix_addr;
unix_addr.sun_family = AF_UNIX;
strncpy(unix_addr.sun_path, "/tmp/my_socket", sizeof(unix_addr.sun_path) - 1);
/* Key difference: no port number, just a path string */
/* The path can be up to 107 characters (108 - 1 for null terminator) */
unixListen() Implementation
Mirrors inetListen() but for UNIX domain. Key difference: you must unlink() the socket file first (in case it exists from a previous run), and you must clean up the file when the server exits.
/*
* unixListen() - Create a listening UNIX domain socket at path.
*
* path - filesystem path for the socket file (e.g. "/tmp/my.sock")
* backlog - listen() backlog
*
* Returns fd ready for accept(), or -1 on error.
*/
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
int unixListen(const char *path, int backlog)
{
int sockfd;
struct sockaddr_un addr;
/* Remove any existing socket file — otherwise bind() will fail */
if (remove(path) == -1 && errno != ENOENT) {
/* ENOENT means file doesn't exist — that's fine */
return -1;
}
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd == -1)
return -1;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
close(sockfd);
return -1;
}
if (listen(sockfd, backlog) == -1) {
close(sockfd);
return -1;
}
return sockfd;
}
/*
* IMPORTANT: When the server exits, the socket file remains on disk!
* You must call unlink(path) on cleanup:
*
* void cleanup(void) {
* unlink("/tmp/my.sock");
* }
* atexit(cleanup);
*/
unixConnect() Implementation
/*
* unixConnect() - Connect to a UNIX domain socket server.
*
* path - the socket file path the server is listening on
* type - SOCK_STREAM or SOCK_DGRAM
*
* Returns a connected fd, or -1 on error.
*/
int unixConnect(const char *path, int type)
{
int sockfd;
struct sockaddr_un addr;
sockfd = socket(AF_UNIX, type, 0);
if (sockfd == -1)
return -1;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
close(sockfd);
return -1;
}
return sockfd;
}
File Transfer Example Using the Library
The TLPI book has us_xfr_sv.c and us_xfr_cl.c — a server that receives data over a UNIX domain socket and writes it to standard output. The client sends data from standard input to the server.
unix_sockets.h
/* unix_sockets.h */
#ifndef UNIX_SOCKETS_H
#define UNIX_SOCKETS_H
int unixListen(const char *path, int backlog);
int unixConnect(const char *path, int type);
#endif
us_xfr_sv.c — Transfer Server
/* us_xfr_sv.c - Receive data over UNIX domain socket, write to stdout */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "unix_sockets.h"
#define SV_SOCK_PATH "/tmp/us_xfr"
#define BUF_SIZE 100
#define BACKLOG 5
static void cleanup(void)
{
unlink(SV_SOCK_PATH);
}
int main(void)
{
int listenfd, connfd;
char buf[BUF_SIZE];
ssize_t n;
atexit(cleanup); /* Remove socket file on exit */
listenfd = unixListen(SV_SOCK_PATH, BACKLOG);
if (listenfd == -1) {
perror("unixListen"); exit(EXIT_FAILURE);
}
fprintf(stderr, "Server listening at %s\n", SV_SOCK_PATH);
for (;;) {
connfd = accept(listenfd, NULL, NULL);
if (connfd == -1) { perror("accept"); exit(EXIT_FAILURE); }
fprintf(stderr, "Client connected\n");
/* Read all data from client, write to stdout */
while ((n = read(connfd, buf, sizeof(buf))) > 0) {
if (write(STDOUT_FILENO, buf, n) != n) {
fprintf(stderr, "partial/failed write\n");
exit(EXIT_FAILURE);
}
}
if (n == -1) { perror("read"); exit(EXIT_FAILURE); }
fprintf(stderr, "Client finished\n");
close(connfd);
}
return 0;
}
us_xfr_cl.c — Transfer Client
/* us_xfr_cl.c - Read from stdin, send to server over UNIX domain socket */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "unix_sockets.h"
#define SV_SOCK_PATH "/tmp/us_xfr"
#define BUF_SIZE 100
int main(void)
{
int sockfd;
char buf[BUF_SIZE];
ssize_t n;
sockfd = unixConnect(SV_SOCK_PATH, SOCK_STREAM);
if (sockfd == -1) {
perror("unixConnect"); exit(EXIT_FAILURE);
}
/* Read from stdin, send to server */
while ((n = read(STDIN_FILENO, buf, sizeof(buf))) > 0) {
if (write(sockfd, buf, n) != n) {
fprintf(stderr, "partial/failed write\n");
exit(EXIT_FAILURE);
}
}
if (n == -1) { perror("read stdin"); exit(EXIT_FAILURE); }
exit(EXIT_SUCCESS);
}
Build and Test
gcc -Wall -o us_xfr_sv us_xfr_sv.c unix_sockets.c
gcc -Wall -o us_xfr_cl us_xfr_cl.c unix_sockets.c
# Terminal 1 — start server, redirect output to a file
./us_xfr_sv > /tmp/output.txt
# Terminal 2 — send a file to the server
./us_xfr_cl < /etc/passwd
# Check result
cat /tmp/output.txt # Should match /etc/passwd
# Verify the socket file was created
ls -la /tmp/us_xfr
# srwxr-xr-x 1 ravi ravi 0 Jun 15 10:00 /tmp/us_xfr
# Note: type 's' means socket file
Abstract UNIX Domain Sockets (Linux Extension)
Linux supports abstract sockets — UNIX domain sockets that are NOT backed by a filesystem path. They live in a separate “abstract namespace”. When the last file descriptor referencing them closes, they disappear automatically — no unlink() needed.
/* To use an abstract socket, set sun_path[0] = '\0' */
/* The name is sun_path[1..] (null bytes are valid in the name) */
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
addr.sun_path[0] = '\0'; /* Magic: abstract namespace */
strncpy(&addr.sun_path[1], "my_abstract_sock", /* Name starts at [1] */
sizeof(addr.sun_path) - 2);
/* When binding, the length must account for the full name including [0] */
socklen_t len = 1 + strlen(&addr.sun_path[1]) + offsetof(struct sockaddr_un, sun_path);
bind(sockfd, (struct sockaddr *)&addr, len);
/*
* Abstract socket differences vs filesystem sockets:
*
* Filesystem socket | Abstract socket
* --------------------------+------------------------
* Path visible in fs | Invisible (no fs entry)
* Survives process death | Disappears when all fds close
* Requires unlink() | No cleanup needed
* Portable (POSIX) | Linux-only
* Permission via fs | No permission control
*/
D-Bus (the standard IPC bus on Linux desktops) uses abstract sockets. Android’s Binder IPC also uses them. You can list abstract sockets with:
ss -xl # or: netstat -xl
# Look for names starting with '@' — that's the abstract namespace indicator
Credential Passing — A UNIX Domain Socket Superpower
One major advantage of UNIX domain sockets over Internet sockets: the kernel can tell you the real UID and PID of the connecting process. This is impossible over TCP.
#include <sys/socket.h>
#include <sys/un.h>
/* On the server side: get peer credentials */
struct ucred cred;
socklen_t cred_len = sizeof(cred);
if (getsockopt(connfd, SOL_SOCKET, SO_PEERCRED, &cred, &cred_len) == -1) {
perror("getsockopt SO_PEERCRED");
} else {
printf("Client PID: %d\n", cred.pid);
printf("Client UID: %d\n", cred.uid);
printf("Client GID: %d\n", cred.gid);
/* Now you can make access control decisions based on UID */
if (cred.uid != 0 && cred.uid != getuid()) {
/* Reject connection from other users */
close(connfd);
}
}
/*
* struct ucred {
* pid_t pid; // Process ID of sending process
* uid_t uid; // Real user ID of sending process
* gid_t gid; // Real group ID of sending process
* };
*/
Interview Questions & Answers
AF_UNIX sockets communicate within the same machine using a filesystem path as the address. AF_INET sockets communicate over the network using IP address and port. UNIX domain sockets are faster (no TCP/IP stack traversal), support credential passing, and can use filesystem permissions for access control. They cannot communicate between machines.
bind() creates a new special file (socket type) at the given path. If a file already exists there from a previous run, bind() fails with EADDRINUSE. Calling remove()/unlink() first removes the old socket file. We ignore ENOENT (file doesn’t exist) since that’s not an error — it means this is a fresh start.
Unlike Internet domain ports which are released automatically, the socket file remains on disk after the server exits. You must explicitly unlink() it during cleanup. A common pattern is to use atexit() to register a cleanup function, or catch SIGTERM and SIGINT to unlink before exiting.
An abstract socket is a Linux-only extension where sun_path[0] = '\0'. It lives in an abstract namespace (not the filesystem), so it leaves no file on disk, requires no cleanup when the process exits, and cannot be controlled by filesystem permissions. D-Bus uses abstract sockets. They are useful when filesystem path naming or cleanup is inconvenient.
Using getsockopt(connfd, SOL_SOCKET, SO_PEERCRED, &cred, &len), a UNIX domain server can get the PID, UID, and GID of the connecting process as a struct ucred. The kernel fills this automatically — the client cannot spoof it. This is impossible over TCP sockets where the only identity information is an IP address, which can be forged.
Yes. socket(AF_UNIX, SOCK_DGRAM, 0) creates an unreliable, connectionless datagram socket in the UNIX domain. Unlike UDP (which can lose packets), UNIX domain datagrams are reliable within the machine — the kernel doesn’t drop them under normal conditions. They are used for things like syslog which uses a UNIX datagram socket at /dev/log.
sun_path is 108 bytes total, so the maximum null-terminated path is 107 characters. This is much shorter than POSIX’s PATH_MAX (typically 4096). Paths must include the null terminator, so deep paths like /var/run/some/deeply/nested/socket may exceed the limit and fail silently if you use strcpy() without checking length. Always use strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1).
PostgreSQL uses a UNIX socket at /var/run/postgresql/.s.PGSQL.5432 for local connections. Docker daemon uses /var/run/docker.sock. D-Bus uses abstract sockets. X11 display server uses /tmp/.X11-unix/X0. Systemd journal uses /run/systemd/journal/socket. Redis uses /var/run/redis/redis.sock when configured for local access. UNIX sockets are everywhere in Linux infrastructure.
