UNIX Domain Sockets

 

Chapter 57: UNIX Domain Sockets
Part 1 of 6 โ€” Introduction & Core Concepts
๐Ÿ“‚ File 1 of 6
๐Ÿ“– TLPI Chapter 57
๐Ÿง Linux IPC

What are UNIX Domain Sockets?

UNIX domain sockets (also called local sockets or IPC sockets) are a way for two processes running on the same machine to communicate. Unlike TCP/IP sockets that send data over a network, UNIX domain sockets work entirely inside the kernel โ€” data never leaves the machine.

Think of it this way: if you want two processes to talk to each other on the same Linux box, UNIX domain sockets are one of the best tools available. They are faster than TCP/IP (no network stack overhead), support both stream and datagram communication, and can even pass open file descriptors between processes โ€” something nothing else can do.

The socket API for UNIX domain sockets is almost identical to the TCP/IP socket API. The main difference is the address family (AF_UNIX instead of AF_INET) and how addresses are specified โ€” using a file system path or an abstract name.

Key Terms in This File

AF_UNIX sockaddr_un SOCK_STREAM SOCK_DGRAM IPC socket path socket file bind() SUN_LEN

Why Use UNIX Domain Sockets?

Linux has many IPC mechanisms โ€” pipes, FIFOs, System V message queues, shared memory. Why use UNIX domain sockets?

Feature Pipe / FIFO Shared Memory TCP Socket UNIX Socket
Bidirectional โŒ (FIFO yes) โœ… โœ… โœ…
Unrelated processes FIFO only โœ… โœ… โœ…
Pass file descriptors โŒ โŒ โŒ โœ…
Works over network โŒ โŒ โœ… โŒ
Performance Fast Very Fast Slower Fast
Credential passing โŒ โŒ โŒ โœ…

UNIX domain sockets combine the best of both worlds: the ease of the socket API (which developers already know from networking) with high performance of local IPC. They are used by many critical Linux services โ€” systemd, D-Bus, X11 display server, MySQL, PostgreSQL, and Docker all use UNIX domain sockets.

Two Socket Types: Stream vs Datagram

UNIX domain sockets come in two main types:

SOCK_STREAM

  • Connection-oriented
  • Like a pipe โ€” byte stream
  • Reliable, ordered delivery
  • Must connect before sending
  • Server: bind โ†’ listen โ†’ accept
  • Client: connect
SOCK_DGRAM

  • Connectionless
  • Message boundaries preserved
  • Reliable on UNIX domain (unlike UDP)
  • No need to connect
  • Use sendto() / recvfrom()
  • Each message goes independently

Important: Unlike UDP datagrams over a network, UNIX domain datagrams are reliable and ordered. The kernel handles them entirely in memory, so they cannot get lost or reordered. However, if the receiver’s buffer is full, the sender will block until space is available.

The Address Structure: sockaddr_un

Every socket needs an address. For UNIX domain sockets, the address is either a file system path or an abstract name. The structure used 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]; /* Socket path (or abstract name) */
};

Key points about sockaddr_un:

  • sun_family must always be AF_UNIX
  • sun_path holds the file system path as a null-terminated string
  • The maximum path length is 108 characters (including the null terminator) on Linux
  • For abstract namespace sockets, sun_path[0] is a null byte ('\0')

When calculating the length to pass to bind(), use the SUN_LEN() macro or calculate manually:

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>

int main(void)
{
    struct sockaddr_un addr;
    socklen_t addr_len;

    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, "/tmp/my_socket", sizeof(addr.sun_path) - 1);

    /* Calculate the correct length for bind() */
    /* Method 1: using offsetof + strlen */
    addr_len = offsetof(struct sockaddr_un, sun_path) + strlen(addr.sun_path) + 1;

    /* Method 2: using SUN_LEN macro (same result) */
    addr_len = SUN_LEN(&addr);

    printf("Address family : %d (AF_UNIX = %d)\n", addr.sun_family, AF_UNIX);
    printf("Socket path    : %s\n", addr.sun_path);
    printf("Address length : %u bytes\n", (unsigned)addr_len);

    return 0;
}

Memory layout of sockaddr_un:

sun_family
(2 bytes)
= AF_UNIX
sun_path[108]
“/tmp/my_socket\0……………..”

Total structure size = 2 + 108 = 110 bytes. But we only pass the bytes actually used (up to and including the null terminator of the path) to bind().

Creating a UNIX Domain Socket

Creating a UNIX domain socket uses the same socket() call as network sockets:

#include <sys/socket.h>

/* Prototype */
int socket(int domain, int type, int protocol);

/* For UNIX domain stream socket */
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd == -1) {
    perror("socket");
    exit(EXIT_FAILURE);
}

/* For UNIX domain datagram socket */
int fd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (fd == -1) {
    perror("socket");
    exit(EXIT_FAILURE);
}

The third argument (protocol) is always 0 for UNIX domain sockets โ€” there is only one protocol for each socket type in this domain.

Socket Files in the File System

When you call bind() on a UNIX domain socket, the kernel creates a special file in the file system at the given path. This file has type socket (shown as s by ls -l).

$ ls -l /tmp/my_socket
srwxrwxrwx 1 ravi ravi 0 Jun 13 10:00 /tmp/my_socket
^
|_ 's' = socket file type

Important rules about socket files:

โš ๏ธ Must remove before re-binding: If a socket file already exists at the path, bind() will fail with EADDRINUSE. Always call unlink() or remove() to delete the old socket file before binding.
โœ… Permissions matter: Access to a UNIX domain socket is controlled by the file system permissions on the socket file, just like regular files.
โš ๏ธ The socket file is not the data: The socket file is just a name. Data flows through the kernel’s socket buffers, not through the file.
โœ… Clean up on exit: Good programs remove their socket file when done (using unlink() in a cleanup handler or at program start).
/* Safe bind pattern โ€” remove old socket file first */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>

#define SOCKET_PATH "/tmp/my_socket"

int create_and_bind_socket(void)
{
    int fd;
    struct sockaddr_un addr;

    fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (fd == -1) {
        perror("socket");
        return -1;
    }

    /* Remove old socket file if it exists */
    if (remove(SOCKET_PATH) == -1 && errno != ENOENT) {
        perror("remove");
        close(fd);
        return -1;
    }

    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);

    if (bind(fd, (struct sockaddr *)&addr, SUN_LEN(&addr)) == -1) {
        perror("bind");
        close(fd);
        return -1;
    }

    return fd;
}

General Communication Flow (Visual Overview)

UNIX Domain Stream Socket โ€” Server/Client Flow:

SERVER
socket(AF_UNIX, SOCK_STREAM)
โ†“
bind(“/tmp/sock”)
โ†“
listen()
โ†“
accept() โ† blocks
โ†“
read() / write()
โ†“
close()

โ‡„

CLIENT
socket(AF_UNIX, SOCK_STREAM)
โ†“
connect(“/tmp/sock”)
โ†“
write() / read()
โ†“
close()

UNIX Domain Datagram Socket โ€” Flow:

RECEIVER (Server)
socket(AF_UNIX, SOCK_DGRAM)
โ†“
bind(“/tmp/sock”)
โ†“
recvfrom() โ† blocks
โ†“
close()
โ†’
SENDER (Client)
socket(AF_UNIX, SOCK_DGRAM)
โ†“
bind(“/tmp/client_sock”) [optional but needed for replies]
โ†“
sendto(“/tmp/sock”, data)
โ†“
close()

Headers You Need
#include <sys/socket.h>   /* socket(), bind(), connect(), listen(), accept() */
#include <sys/un.h>       /* struct sockaddr_un, AF_UNIX */
#include <unistd.h>       /* close(), read(), write(), unlink() */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

Compile with:

gcc -Wall -o server server.c
gcc -Wall -o client client.c

๐ŸŽฏ Interview Questions โ€” Introduction
Q1. What is the difference between AF_UNIX and AF_INET sockets?

AF_UNIX (or AF_LOCAL) sockets communicate between processes on the same machine using a file system path or abstract name as the address. Data stays in the kernel โ€” no network stack is involved. AF_INET sockets use IP addresses and port numbers and can communicate over a network. AF_UNIX is faster and more efficient for local IPC because there is no TCP/IP header overhead, no checksumming, and no routing.

Q2. Are UNIX domain datagram sockets reliable?

Yes. Unlike UDP datagrams over the network, UNIX domain datagrams are reliable and ordered. The kernel manages them entirely in memory. Messages are never lost or reordered. However, if the receiver’s socket buffer is full, the sender will block (unlike UDP which just drops packets). This blocking behavior is what makes them reliable.

Q3. What happens if you try to bind() to a path that already has a socket file?

bind() fails with the error EADDRINUSE (“Address already in use”). The solution is to call unlink() or remove() on the path before calling bind(). Servers typically do this at startup. Also, removing the socket file does not destroy the socket itself โ€” connected processes can keep using it.

Q4. What is SUN_LEN and why is it needed?

SUN_LEN(ptr) is a macro that computes the correct length of a sockaddr_un structure to pass to bind(), connect(), or sendto(). It calculates: offsetof(sockaddr_un, sun_path) + strlen(sun_path) + 1. You must not pass sizeof(struct sockaddr_un) because that includes the unused bytes at the end of the sun_path array, which can confuse some implementations.

Q5. Why are UNIX domain sockets faster than TCP loopback (127.0.0.1)?

With TCP loopback, the data still goes through the full TCP/IP stack: framing, segmentation, TCP sequence numbers, ACKs, checksum calculation and verification, and multiple memory copies in the kernel network stack. UNIX domain sockets skip all of this. The kernel copies data directly from the sender’s buffer to the receiver’s buffer. Benchmarks typically show UNIX domain sockets are 2โ€“5x faster than TCP loopback for small messages.

Q6. Name some real-world programs that use UNIX domain sockets.

systemd (for IPC between services), D-Bus (desktop interprocess communication), X11 and Wayland display servers, MySQL and PostgreSQL (for local client connections), Docker daemon, Chrome/Chromium (between browser and renderer processes), BlueZ (Bluetooth daemon), CUPS (printing system), and Redis (when configured for local connections).

Leave a Reply

Your email address will not be published. Required fields are marked *