UNIX Domain Socket Library unixListen() · unixConnect() · sockaddr_un · Abstract Sockets

 

Part 3: UNIX Domain Socket Library
unixListen() · unixConnect() · sockaddr_un · Abstract Sockets · EmbeddedPathashala

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
When to use UNIX domain sockets: Inter-process communication on the same machine — database connections (PostgreSQL uses them), D-Bus, X11, container runtime sockets (like Docker), and any scenario where network overhead is undesirable and processes share a filesystem.

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) */

Address Structure Layout
sockaddr_in (Internet)
sin_family = AF_INET
sin_port = 8080
sin_addr = 0.0.0.0
sockaddr_un (UNIX)
sun_family = AF_UNIX
sun_path = “/tmp/my_socket”
(no port, just path)

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
 */
Real-world use of abstract sockets:
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

Q1. What is the main difference between AF_UNIX and AF_INET sockets?

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.

Q2. Why do you need to unlink() the socket path before bind()?

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.

Q3. What happens to the socket file when the server process exits?

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.

Q4. What is an abstract UNIX domain socket and how is it different?

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.

Q5. How can a UNIX domain server know which user is connecting?

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.

Q6. Can you use SOCK_DGRAM with UNIX domain sockets?

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.

Q7. What is the maximum path length for sun_path?

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).

Q8. Name real-world software that uses UNIX domain sockets.

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.

Leave a Reply

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