When you need two processes on different machines to communicate, you have no choice — you must use Internet domain sockets (AF_INET or AF_INET6) with TCP or UDP.
But what if both processes are on the same machine? You then have two options:
Uses TCP/IP stack
Works over network too
Same API as remote communication
Uses filesystem path
Same-host only
Extra Linux features
The TLPI text says that writing with only Internet domain sockets is often the simplest approach since it works on both single host and across a network — but there are specific reasons to prefer UNIX domain sockets for local IPC.
Address = IP address + port number
Data travels through the TCP/IP networking stack
Even on the same machine, data goes through the loopback interface (127.0.0.1)
Supports stream (TCP) and datagram (UDP)
Address = a path in the filesystem (e.g., /tmp/mysock)
Data travels through kernel memory only
No TCP/IP processing overhead
Supports stream and datagram too
On many Linux and Unix implementations, UNIX domain sockets are faster than Internet domain sockets for local IPC.
Why? With Internet domain sockets on the same machine, data still goes through the full TCP/IP stack — TCP segmentation, IP header processing, checksum calculation, loopback routing — even though the data never leaves the machine. With UNIX domain sockets, the kernel skips all of this and copies data directly through kernel memory.
Process A → TCP layer → IP layer → Loopback (127.0.0.1) → IP layer → TCP layer → Process B
Process A → Kernel buffer → Process B
Important caveat: Modern Linux kernels have significantly optimized the loopback path. The performance difference may be small for many workloads. Always benchmark your specific use case before making assumptions.
This is one of the strongest reasons to use UNIX domain sockets for local IPC. Because a UNIX socket appears as a file in the filesystem, you can use standard file permission bits and ownership to control which users or groups can connect to it.
/* Example: Create a UNIX domain socket accessible only to a specific group */
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define SOCKET_PATH "/var/run/myapp.sock"
int create_restricted_socket(void)
{
int sfd;
struct sockaddr_un addr;
sfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sfd == -1) {
perror("socket");
return -1;
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
unlink(SOCKET_PATH); /* Remove old socket file if it exists */
if (bind(sfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
perror("bind");
return -1;
}
/* Set permissions: owner can read/write, group can read/write, others nothing */
/* This means only processes running as the socket owner or in its group */
/* can connect — a simple form of authentication */
if (chmod(SOCKET_PATH, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) == -1) {
perror("chmod");
return -1;
}
return sfd;
}
connect() to this socket. The kernel enforces this automatically. With Internet domain sockets, you would have to implement your own authentication protocol (tokens, certificates, etc.).On Linux, the socket file can also be placed in a directory with restricted access, adding another layer of control based on directory permissions.
UNIX domain sockets support two capabilities that are impossible with Internet domain sockets:
Process A can send an open file descriptor (a socket, a file, a pipe) to Process B over a UNIX socket using sendmsg() with SCM_RIGHTS ancillary data. Process B gets a fully usable copy of the descriptor — it can read or write the file as if it opened it itself.
Real-world use: systemd socket activation, privilege separation daemons.
A process can send its PID, UID, and GID over a UNIX socket using SCM_CREDENTIALS. The receiving process can verify who it is talking to. Unlike Internet sockets where a client can claim any identity, the kernel verifies these credentials — they cannot be faked.
Real-world use: D-Bus, PolicyKit, Xorg authentication.
/* Skeleton: Receiving peer credentials on Linux */
#include <sys/socket.h>
#include <sys/un.h>
int get_peer_credentials(int conn_fd)
{
struct ucred cred;
socklen_t len = sizeof(cred);
/* SO_PEERCRED is a Linux-specific socket option */
if (getsockopt(conn_fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1) {
perror("getsockopt SO_PEERCRED");
return -1;
}
printf("Peer PID: %ld\n", (long)cred.pid);
printf("Peer UID: %ld\n", (long)cred.uid);
printf("Peer GID: %ld\n", (long)cred.gid);
return 0;
}
| Feature | UNIX Domain (AF_UNIX) | Internet Domain (AF_INET/6) |
|---|---|---|
| Address | Filesystem path (e.g., /tmp/app.sock) | IP address + port number |
| Works across network | No — same machine only | Yes — any TCP/IP network |
| Performance (local) | Generally faster (no TCP/IP stack) | Slightly slower (loopback path) |
| Access control | File permissions + ownership | Must implement manually (tokens, TLS) |
| FD passing | Yes (SCM_RIGHTS) | No |
| Credential passing | Yes (SCM_CREDENTIALS / SO_PEERCRED) | No (client can spoof identity) |
| Stream sockets | Yes (SOCK_STREAM) | Yes (TCP) |
| Datagram sockets | Yes (SOCK_DGRAM) | Yes (UDP) |
| Code complexity | Slightly more (manage socket file) | Simpler (no filesystem cleanup) |
- Your application must communicate across a network (different machines)
- You want a single codebase that works for both local and remote communication
- Simplicity is more important than raw local performance
- You are building a client-server system that may be distributed in the future
- Communication is strictly local (same machine, always)
- You need fine-grained access control without building an auth protocol
- You need to pass open file descriptors between processes
- You need to verify the identity (UID/GID/PID) of the connecting process
- Maximum local throughput is critical (high-frequency IPC)
The socket API is deliberately similar for both domains. Here is how a server creation looks side-by-side:
/* ====== Internet Domain Server ====== */
int create_inet_server(int port)
{
int sfd;
struct sockaddr_in addr;
sfd = socket(AF_INET, SOCK_STREAM, 0); /* AF_INET for internet */
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port); /* Port number */
addr.sin_addr.s_addr = INADDR_ANY; /* All interfaces */
bind(sfd, (struct sockaddr *)&addr, sizeof(addr));
listen(sfd, 5);
return sfd;
}
/* ====== UNIX Domain Server ====== */
int create_unix_server(const char *path)
{
int sfd;
struct sockaddr_un addr;
sfd = socket(AF_UNIX, SOCK_STREAM, 0); /* AF_UNIX for local */
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX; /* Address family */
strncpy(addr.sun_path, path, /* Filesystem path */
sizeof(addr.sun_path) - 1);
unlink(path); /* Clean up old socket file if it exists */
bind(sfd, (struct sockaddr *)&addr, sizeof(addr));
listen(sfd, 5);
return sfd;
}
The key differences are just the address family (AF_INET vs AF_UNIX) and the address structure (sockaddr_in vs sockaddr_un). The bind(), listen(), accept(), read(), write() calls are identical.
One extra step for UNIX sockets: you should call unlink(path) before binding to remove a stale socket file from a previous run.
A UNIX domain socket uses a filesystem path (stored in struct sockaddr_un, field sun_path) as its address. An Internet domain socket uses an IP address and port number (stored in struct sockaddr_in). UNIX sockets are local-only; Internet sockets can communicate across a network.
Internet domain sockets on the same machine still go through the full TCP/IP networking stack — including TCP segmentation, IP header processing, checksum computation, and routing through the loopback interface. UNIX domain sockets bypass all of this; the kernel copies data directly through shared memory buffers without any network protocol processing.
In two ways. First, the socket file appears in the filesystem, so standard Unix file permissions (read/write bits, ownership) control who can connect. Second, on Linux you can use SO_PEERCRED with getsockopt() to retrieve the PID, UID, and GID of the connecting process. These credentials are provided by the kernel and cannot be faked. Internet domain sockets have no equivalent — a client can connect from any IP and claim any identity.
SCM_RIGHTS is a type of ancillary (control) message sent with sendmsg() over a UNIX domain socket that transfers open file descriptors from one process to another. It works because the kernel can look up file descriptors in the sender’s file descriptor table and duplicate them into the receiver’s table — a kernel-internal operation. This is impossible with Internet domain sockets because the remote host has its own separate kernel and file descriptor table.
(1) D-Bus — uses UNIX sockets for inter-process messaging on the desktop; needs credential passing to enforce policy decisions.
(2) systemd — uses UNIX sockets for socket activation (passing pre-opened sockets to services via FD passing with SCM_RIGHTS).
(3) MySQL / PostgreSQL — create a UNIX socket for local client connections because it is faster and more secure than loopback TCP for clients on the same machine.
A UNIX domain socket creates a socket file in the filesystem when bind() is called. If the server crashes or exits without calling unlink(), this file remains. The next time the server starts and tries to call bind() with the same path, it will fail with EADDRINUSE. The fix is to call unlink(path) before bind() to remove any stale socket file. Internet domain sockets do not create filesystem entries so they do not have this problem.
Review everything covered in Chapter 59 — Internet domain sockets, DNS, data representation, and more.
