socketpair() โ€” Creating a Connected Socket Pair

 

socketpair() โ€” Creating a Connected Socket Pair
Chapter 57.5 โ€” Linux System Programming | EmbeddedPathashala
๐Ÿ”— socketpair()
๐Ÿ”ƒ Bidirectional Pipe
๐Ÿ— fork() + IPC

What is socketpair()?

Sometimes you want a single process to create two connected sockets and use them for communication between a parent and child process. Normally, this would require many steps: socket(), bind(), listen(), connect(), and accept().

socketpair() does all of this in one system call. It creates two pre-connected sockets and returns their file descriptors. Think of it as creating a private two-way pipe between two processes.

Important: socketpair() only works with AF_UNIX. It makes no sense for network sockets since both ends must be on the same machine.

๐Ÿ“„ Function Signature
#include <sys/socket.h>

int socketpair(int domain, int type, int protocol, int sockfd[2]);

/*
 * Returns: 0 on success, -1 on error
 *
 * domain:   Must be AF_UNIX (only UNIX domain supported)
 * type:     SOCK_STREAM or SOCK_DGRAM
 *           (can OR with SOCK_CLOEXEC or SOCK_NONBLOCK on Linux 2.6.27+)
 * protocol: Must be 0
 * sockfd:   Output array - receives the two connected file descriptors
 *           sockfd[0] and sockfd[1] are connected to each other
 */

After socketpair() returns:
sockfd[0]
file descriptor
โ†”
fully connected
both directions
sockfd[1]
file descriptor
Write to sockfd[0] โ†’ Read from sockfd[1]
Write to sockfd[1] โ†’ Read from sockfd[0]

๐Ÿ”ƒ SOCK_STREAM vs SOCK_DGRAM with socketpair()
SOCK_STREAM

  • Bidirectional byte stream
  • Like a pipe but in both directions
  • Data flows as a continuous stream (no message boundaries)
  • Called a stream pipe
  • On BSD systems, regular pipe() is actually implemented using socketpair()
SOCK_DGRAM

  • Preserves message boundaries
  • Each write is a separate datagram
  • Reliable (local machine, no packet loss)
  • Better when you need to distinguish individual messages

๐Ÿ”จ Basic Usage: IPC Between Parent and Child

The most common pattern: create a socket pair, then fork. Parent and child each close one end and use the other. The result is a private, bidirectional channel.

#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(void)
{
    int sv[2];   /* sv[0] and sv[1] are the two connected sockets */
    pid_t pid;
    char buf[100];
    ssize_t n;

    /* Step 1: Create the connected socket pair */
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) {
        perror("socketpair");
        exit(1);
    }

    /* Step 2: Fork */
    pid = fork();
    if (pid == -1) { perror("fork"); exit(1); }

    if (pid == 0) {
        /* ---- CHILD ---- */
        close(sv[0]);   /* Child doesn't use sv[0] */

        /* Read a message from the parent */
        n = read(sv[1], buf, sizeof(buf) - 1);
        if (n > 0) {
            buf[n] = '\0';
            printf("Child received: %s\n", buf);
        }

        /* Send a reply */
        write(sv[1], "Hello from child!", 18);
        close(sv[1]);
        exit(EXIT_SUCCESS);

    } else {
        /* ---- PARENT ---- */
        close(sv[1]);   /* Parent doesn't use sv[1] */

        /* Send a message to the child */
        write(sv[0], "Hello from parent!", 19);

        /* Read the child's reply */
        n = read(sv[0], buf, sizeof(buf) - 1);
        if (n > 0) {
            buf[n] = '\0';
            printf("Parent received: %s\n", buf);
        }

        close(sv[0]);
        wait(NULL);   /* Wait for child to finish */
        exit(EXIT_SUCCESS);
    }
}
Expected output:

Child received: Hello from parent!
Parent received: Hello from child!

๐Ÿ— How fork() Uses the Socket Pair

Before fork() โ€” single process
Process
holds both sv[0] and sv[1]
sv[0] โ†”
sv[1]
After fork() โ€” two processes, each closes one end
PARENT

close(sv[1]);
uses sv[0]

sv[0] โœ“
sv[1] โœ— closed
โ‡„
CHILD

close(sv[0]);
uses sv[1]

sv[0] โœ— closed
sv[1] โœ“

๐Ÿ”’ Security: No Filesystem Path

One of the biggest differences between socketpair() and manually creating two connected sockets: socketpair() does not bind to any file system path.

Manual socket creation:

Creates a file at /tmp/mysock
Any process that can read the file system can attempt to connect
Must manually unlink the socket file when done

socketpair():

No file system entry created
Only the process and its children (via fork()) can use the fds
No cleanup needed โ€” closed when fds are closed

๐Ÿ’ก Security Tip: Because no path is visible in the file system, other processes cannot accidentally or maliciously connect to the socket pair. This eliminates an entire class of TOCTOU (time-of-check, time-of-use) vulnerabilities associated with named socket files.

โš™ Linux-Specific Flags: SOCK_CLOEXEC and SOCK_NONBLOCK

Since Linux kernel 2.6.27, you can OR extra flags into the type argument to avoid extra fcntl() calls:

/* Create a non-blocking socket pair that closes on exec */
int sv[2];

if (socketpair(AF_UNIX,
               SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
               0, sv) == -1) {
    perror("socketpair");
    exit(1);
}

/*
 * SOCK_CLOEXEC:
 *   Sets FD_CLOEXEC on both sv[0] and sv[1].
 *   When the process calls exec(), both fds are automatically closed.
 *   Equivalent to: fcntl(sv[0], F_SETFD, FD_CLOEXEC);
 *                  fcntl(sv[1], F_SETFD, FD_CLOEXEC);
 *
 * SOCK_NONBLOCK:
 *   Sets O_NONBLOCK on both sockets.
 *   read()/write() return immediately with EAGAIN if no data.
 *   Equivalent to: fcntl(sv[0], F_SETFL, O_NONBLOCK);
 *                  fcntl(sv[1], F_SETFL, O_NONBLOCK);
 */
โš  These flags are Linux-specific. Do not use them in portable code that must run on BSD or other UNIX systems. On Linux, they are preferred over separate fcntl() calls because they avoid a race condition in multi-threaded programs between socketpair() and fcntl().

๐Ÿ”ƒ socketpair() vs pipe() โ€” Quick Comparison
Feature pipe() socketpair(SOCK_STREAM)
Direction One-way only โœ“ Both directions
File descriptors 2 (one read, one write) 2 (each can read & write)
Bidirectional IPC Needs two pipe() calls โœ“ Single call
Message boundaries No (byte stream) SOCK_DGRAM: Yes
SOCK_STREAM: No
Filesystem path None None
BSD implementation Uses socketpair() internally The primitive

๐ŸŽ“ Interview Questions & Answers
Q1. What does socketpair() do and when would you use it?

A: socketpair() creates two pre-connected sockets in a single call. You use it when a process wants to communicate with its child (after fork()) over a private, bidirectional channel. It is simpler and more secure than manually creating, binding, and connecting two sockets.

Q2. Why can socketpair() only be used with AF_UNIX?

A: Because socketpair() creates both endpoints in the same kernel call on the same machine. Network sockets (AF_INET) require two different hosts or at least IP-level addressing, which does not make sense for a local pair created in a single system call.

Q3. What is a stream pipe?

A: A stream pipe is a bidirectional pipe created using socketpair(AF_UNIX, SOCK_STREAM, 0, sv). Unlike a traditional pipe() (which is one-directional), a stream pipe allows data to flow in both directions. On BSD-based systems, the regular pipe() system call is actually implemented internally using socketpair().

Q4. What is SOCK_CLOEXEC and why is it useful in socketpair()?

A: SOCK_CLOEXEC sets the close-on-exec flag (FD_CLOEXEC) on both socket file descriptors atomically at creation time. This means if the process calls exec(), the socket fds are automatically closed. Without this flag, you need a separate fcntl() call, which creates a race condition in multi-threaded programs (another thread might fork() between socketpair() and fcntl()).

Q5. What is the protocol argument in socketpair() and what value must it have?

A: The protocol argument selects the specific protocol within the socket family. For AF_UNIX, there is only one protocol, so this argument must always be 0.

Q6. After fork(), why should each process close one end of the socketpair?

A: After fork(), both parent and child hold copies of both file descriptors. For clean communication, each process should close the fd it does not use. If they don’t, the parent closing its end does not fully close the socket because the child still holds it open (and vice versa). This could cause read() to block indefinitely instead of returning EOF.

Q7. How is socketpair() safer than named UNIX domain sockets?

A: socketpair() creates sockets that are not bound to any file system path. No other process can discover or connect to them. Named sockets (created with bind()) have a path in the file system that any process with appropriate permissions can find and connect to, introducing security risks.

Continue Learning

Next: Linux Abstract Socket Namespace โ€” sockets without filesystem paths

Next: Abstract Namespace โ†’ โ† Previous

Leave a Reply

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