Networking in C · intermediate · ~20 min
Communicate between processes on the same machine via AF_UNIX sockets.
AF_UNIX sockets give you the standard Berkeley sockets API for local inter-process communication (IPC) — that is, communication between processes on the same machine.
You use the same calls as with network sockets: socket, bind, listen, accept, connect, read, and write. The only real difference is the address. Instead of an IP and port, the endpoint is a filesystem path.
Two benefits follow:
localhost over TCP, because there is no network stack to traverse.Unix sockets are everywhere on Linux:
One practical consequence: the file permissions on the socket path become your access control. Whoever can open the path can connect.
SOCK_STREAM behaves like TCP: an ordered, reliable byte stream.SOCK_DGRAM behaves like UDP: individual messages.Stream is almost always what you want.
The address lives in sun_path, a fixed 108-byte buffer. The socket shows up as a file on disk, so chmod and chown apply to it like any other file.
If you start sun_path with a NUL byte (\0), you create an abstract socket. It lives in kernel space, not on disk. There is no file to clean up afterward.
Call getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) to learn the peer's uid, gid, and pid. Use this for access control inside your own protocol.
You can pass file descriptors between processes by sending them as ancillary data (extra control data attached to a message) in sendmsg/recvmsg. This is the mechanism behind the systemd socket-activation pattern.
SO_PEERCRED is your authentication knob inside the protocol.0660 (or 0600).bind, or use the abstract namespace.SO_PEERCRED on every accept.The address structure for AF_UNIX sockets:
#include <sys/un.h>
struct sockaddr_un {
sa_family_t sun_family; /* always AF_UNIX */
char sun_path[108]; /* filesystem path */
};
Unix domain sockets (AF_UNIX) speak the same API as TCP, but the endpoint is a filesystem path instead of an IP address.
This gives you three things:
SO_PEERCRED.int s = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr = { .sun_family = AF_UNIX };
strcpy(addr.sun_path, "/tmp/cplat.sock");
bind(s, (struct sockaddr *)&addr, sizeof addr);
listen(s, 4);
int s = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr = { .sun_family = AF_UNIX };
strncpy(addr.sun_path, "/tmp/cplat.sock", sizeof addr.sun_path - 1);
unlink(addr.sun_path); /* stale from prior run */
bind(s, (struct sockaddr *)&addr, sizeof addr);
chmod(addr.sun_path, 0660); /* who can connect? */
listen(s, 4);
unlink the path before bind. On the second run, the stale socket file is still there, and bind fails with EADDRINUSE.Two commands help you inspect Unix sockets:
ss -xnp lists Unix domain sockets and shows which process holds each end.lsof /tmp/cplat.sock shows the owners of a specific socket file.sun_path is a fixed 108-byte buffer.
Copy at most sizeof sun_path - 1 bytes into it, and make sure the result is NUL-terminated. This leaves room for the terminator and avoids overflowing the buffer.
/var/run/docker.sock)/tmp/echo.sock.SO_PEERCRED to print the uid of each connecting client.SO_PEERCRED for authentication inside your protocol.