Advanced Socket Features SCTP, DCCP, Sequenced-Packet Sockets

 

Advanced Socket Features
Chapter 61 · Part 7 of 7 — SCTP, DCCP, Sequenced-Packet Sockets, Credential Passing, and UDP trade-offs

Beyond TCP and UDP

This final part covers advanced socket features that are less commonly used but important to know — especially for systems programming interviews and embedded Linux work. We look at two modern transport protocols (SCTP and DCCP), the SOCK_SEQPACKET socket type, how UNIX domain sockets can pass process credentials securely, and why UDP is sometimes the better choice over TCP.

UDP vs TCP — When UDP Wins

Why Would Anyone Choose UDP?

TCP guarantees ordered, reliable, in-order delivery. Surely that is always better? Not always. TCP’s guarantees come with costs:

TCP has connection overhead (handshake, teardown). TCP’s retransmission can cause head-of-line blocking — if one packet is lost, all subsequent packets wait even if they arrived correctly. TCP’s congestion control adds latency. For some applications, none of these are needed.

UDP is better for:

• DNS lookups (single request/response)

• Real-time audio/video (stale = useless)

• Online gaming (old position data is worthless)

• Simple broadcast/multicast

• Protocols that do their own reliability (QUIC)

• Low-latency sensor data over LAN

TCP is better for:

• File transfer (every byte must arrive)

• HTTP/HTTPS

• Email (SMTP, IMAP)

• SSH

• Database connections

• Any ordered, reliable data stream

UDP preserves message boundaries (each sendto() = one recvfrom()), has no connection overhead, and requires no teardown. When you need low latency and can tolerate or ignore lost packets, UDP is the right choice.

SOCK_SEQPACKET — The Best of Both Worlds

What Is a Sequenced-Packet Socket?

SOCK_SEQPACKET combines properties of both stream and datagram sockets:

Property SOCK_STREAM (TCP) SOCK_DGRAM (UDP) SOCK_SEQPACKET
Connection-oriented ✓ Yes ✗ No ✓ Yes
Message boundaries preserved ✗ No (byte stream) ✓ Yes ✓ Yes
Reliable delivery ✓ Yes ✗ No ✓ Yes
In-order delivery ✓ Yes ✗ No ✓ Yes
Linux support Always Always UNIX domain: kernel 2.6.4+
Internet: via SCTP only

The key behaviour of SOCK_SEQPACKET: if you call read() with a buffer smaller than the message, the excess bytes are silently discarded. This is different from SOCK_STREAM where you just get partial data and need to call read() again.

Creating a SOCK_SEQPACKET Socket
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#define SOCK_PATH "/tmp/seqpacket_demo"

/* SERVER SIDE */
int seqpacket_server(void)
{
    int                lfd, cfd;
    struct sockaddr_un addr;
    char               buf[256];
    ssize_t            nr;

    unlink(SOCK_PATH);

    /*
     * SOCK_SEQPACKET on UNIX domain (AF_UNIX).
     * Connection setup is identical to SOCK_STREAM.
     */
    lfd = socket(AF_UNIX, SOCK_SEQPACKET, 0);

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

    bind(lfd, (struct sockaddr *)&addr, sizeof(addr));
    listen(lfd, 5);

    printf("Server: waiting for connection\n");
    cfd = accept(lfd, NULL, NULL);

    /* Each read() returns exactly ONE message (if buffer is large enough) */
    while ((nr = read(cfd, buf, sizeof(buf))) > 0) {
        buf[nr] = '\0';
        printf("Server received message (%zd bytes): %s\n", nr, buf);
        /* Echo it back */
        write(cfd, buf, nr);
    }

    close(cfd);
    close(lfd);
    unlink(SOCK_PATH);
    return 0;
}

/* CLIENT SIDE */
int seqpacket_client(void)
{
    int                sfd;
    struct sockaddr_un addr;
    char               reply[256];
    ssize_t            nr;

    sfd = socket(AF_UNIX, SOCK_SEQPACKET, 0);

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

    connect(sfd, (struct sockaddr *)&addr, sizeof(addr));

    /* Send messages — each write() is one message */
    const char *msg1 = "Hello";
    const char *msg2 = "World";

    write(sfd, msg1, strlen(msg1));
    write(sfd, msg2, strlen(msg2));

    /* Each read() returns exactly one message */
    nr = read(sfd, reply, sizeof(reply));
    printf("Client got reply 1 (%zd bytes): %.*s\n", nr, (int)nr, reply);

    nr = read(sfd, reply, sizeof(reply));
    printf("Client got reply 2 (%zd bytes): %.*s\n", nr, (int)nr, reply);

    close(sfd);
    return 0;
}

SCTP — Stream Control Transmission Protocol

What Is SCTP?

SCTP was originally designed for telephony signalling (SS7 over IP) but is general-purpose. It combines the reliability of TCP with message boundary preservation (like UDP/SEQPACKET), and adds several features TCP lacks.

Key SCTP features TCP does not have:

Multi-streaming: A single SCTP association can carry multiple independent logical streams. If a message is lost in stream 1, streams 2 and 3 are not blocked waiting for retransmission. This eliminates TCP’s head-of-line blocking problem.

Multi-homing: A single SCTP endpoint can have multiple IP addresses. If one path fails, the association automatically continues on another path without reconnecting.

4-way handshake with cookie: SCTP uses a 4-way handshake (instead of TCP’s 3-way) which includes a stateless cookie mechanism that prevents SYN flood attacks more robustly than TCP SYN cookies.

Message boundaries: Like UDP, each SCTP message is delivered as a unit — no need to frame messages in the application layer.

#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/sctp.h>

/* Create an SCTP stream socket (like TCP but message-oriented) */
int sctp_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);

/* Create an SCTP one-to-one socket (most common — similar to TCP API) */
/* Then use it like a normal TCP socket: bind, listen, connect, accept */

/* Alternatively, use SCTP-specific send/recv to specify stream number */
/* sctp_sendmsg() and sctp_recvmsg() for multi-stream control */
SCTP on Linux:

Available since kernel 2.6. May need: sudo apt install libsctp-dev and compile with -lsctp. Kernel module: sudo modprobe sctp. The SCTP library provides additional APIs via <netinet/sctp.h>.

DCCP — Datagram Congestion Control Protocol

What Is DCCP?

DCCP fills the gap between TCP and UDP: it provides congestion control (like TCP, to be a good internet citizen) but not reliability or ordering (like UDP, for low-latency applications).

This is the combination needed by real-time streaming applications: you don’t want to retransmit stale video frames (no reliability needed), but you also don’t want to flood the network without backing off when there is congestion (congestion control is needed).

Protocol Reliable? Ordered? Congestion Control? Msg Boundaries?
TCP
UDP
SCTP
DCCP

DCCP is available in Linux since kernel 2.6.14. In practice, QUIC (which runs over UDP and implements its own congestion control) has become more widely adopted than DCCP for modern applications.

Credential Passing Over UNIX Domain Sockets

What Is Credential Passing?

When two processes on the same machine communicate over a UNIX domain socket, the server might want to verify who the client is — specifically, the client’s process ID, user ID, and group ID. Credential passing allows this in a tamper-proof way: the credentials are checked by the kernel, not just stated by the client.

This is different from simply asking the client “who are you?” — a malicious client could lie. With credential passing, the kernel itself vouches for the credentials.

On Linux, this uses the SO_PEERCRED socket option or ancillary data (cmsg) with the SCM_CREDENTIALS type.

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

/* SERVER: get client credentials after accept() */
void get_peer_credentials(int conn_fd)
{
    struct ucred cred;
    socklen_t    cred_len = sizeof(cred);

    /*
     * SO_PEERCRED: kernel fills in the credentials of the process
     * on the other end of the UNIX domain socket.
     * These values are set by the kernel — the client cannot fake them
     * (unless client has CAP_SETUID/CAP_SETGID/CAP_SYS_ADMIN).
     */
    if (getsockopt(conn_fd, SOL_SOCKET, SO_PEERCRED,
                   &cred, &cred_len) == -1) {
        perror("getsockopt SO_PEERCRED");
        return;
    }

    printf("Client credentials:\n");
    printf("  PID: %d\n", cred.pid);
    printf("  UID: %d\n", cred.uid);
    printf("  GID: %d\n", cred.gid);

    /* Example: only allow root (UID 0) to connect */
    if (cred.uid != 0) {
        printf("Access denied: non-root client\n");
    } else {
        printf("Access granted: root client\n");
    }
}

The struct ucred contains: pid (process ID), uid (effective user ID), gid (effective group ID).

Security note: A privileged process (CAP_SETUID, CAP_SETGID, CAP_SYS_ADMIN) can forge these credentials. For normal (non-privileged) processes, the kernel-provided values are trustworthy.

/* CLIENT: explicitly send credentials via ancillary data */
#include <sys/socket.h>
#include <string.h>
#include <unistd.h>

void send_credentials(int sockfd)
{
    struct msghdr   msg;
    struct iovec    iov;
    struct cmsghdr *cmsg;
    struct ucred    cred;
    char            ctrl[CMSG_SPACE(sizeof(cred))];
    char            data[] = "hello with creds";

    /* Fill credentials — kernel will verify these match our actual IDs */
    cred.pid = getpid();
    cred.uid = getuid();
    cred.gid = getgid();

    /* Set up the data to send */
    iov.iov_base = data;
    iov.iov_len  = sizeof(data);

    memset(&msg, 0, sizeof(msg));
    msg.msg_iov     = &iov;
    msg.msg_iovlen  = 1;
    msg.msg_control = ctrl;
    msg.msg_controllen = sizeof(ctrl);

    /* Set up the ancillary (control) message with credentials */
    cmsg = CMSG_FIRSTHDR(&msg);
    cmsg->cmsg_level = SOL_SOCKET;
    cmsg->cmsg_type  = SCM_CREDENTIALS;
    cmsg->cmsg_len   = CMSG_LEN(sizeof(cred));
    memcpy(CMSG_DATA(cmsg), &cred, sizeof(cred));

    sendmsg(sockfd, &msg, 0);
    printf("Sent credentials: PID=%d UID=%d GID=%d\n",
           cred.pid, cred.uid, cred.gid);
}

accept() and Socket Option Inheritance

On Linux, when accept() creates a new socket for an incoming connection, the new socket inherits the socket options (set via setsockopt()) of the listening socket. However, it does NOT inherit:

• Open file status flags (e.g. O_NONBLOCK, set via fcntl())
• File descriptor flags (e.g. FD_CLOEXEC)
• Signal-driven I/O settings (SIGIO/O_ASYNC)

This behaviour is specific to Linux — POSIX does not specify it, and other implementations vary. If you need O_NONBLOCK on accepted sockets, set it explicitly on each accepted socket, or use the SOCK_NONBLOCK flag in accept4() (Linux-specific):

#include <sys/socket.h>

/* Linux-specific: accept4() allows flags on the accepted socket */
int conn_fd = accept4(listen_fd, NULL, NULL,
                      SOCK_NONBLOCK | SOCK_CLOEXEC);
/*
 * SOCK_NONBLOCK: sets O_NONBLOCK on the new socket atomically
 * SOCK_CLOEXEC:  sets FD_CLOEXEC on the new socket atomically
 * Avoids a race condition between accept() + fcntl() in threaded servers
 */

Interview Questions

Q1. What problem does SCTP solve that TCP cannot?

Answer: SCTP solves head-of-line blocking through multi-streaming. With TCP, a single lost packet blocks all data behind it. SCTP allows multiple independent streams in one association — a lost packet in stream 1 does not block stream 2 or 3. SCTP also preserves message boundaries (TCP is a byte stream), supports multi-homing (multiple IP addresses per endpoint for redundancy), and has a more robust handshake that resists SYN flood attacks.

Q2. What is the difference between SCTP and DCCP?

Answer: Both provide congestion control. SCTP provides reliable, ordered, in-order delivery with message boundaries — essentially a better TCP. DCCP provides congestion control but no reliability or ordering — essentially a “responsible UDP”. DCCP is for real-time applications that need to be good internet citizens (not flood the network) but do not need retransmission of stale data. SCTP is for applications that need everything TCP provides plus more.

Q3. What is SOCK_SEQPACKET and when would you use it over SOCK_STREAM?

Answer: SOCK_SEQPACKET is connection-oriented and reliable (like SOCK_STREAM/TCP) but also preserves message boundaries (like SOCK_DGRAM/UDP). Each write produces exactly one readable message — no framing is needed. You would use it when you want reliable, ordered delivery AND need message boundaries to be preserved, for example for IPC between processes where messages have variable length and you don’t want to write message-length framing code. On Linux, SOCK_SEQPACKET works with UNIX domain sockets (since kernel 2.6.4) and with SCTP (Internet domain).

Q4. How does SO_PEERCRED work and why is it more secure than the client just saying “I am user X”?

Answer: SO_PEERCRED retrieves credentials that the kernel fills in based on the actual process making the connection, not something the client provides. The kernel knows the true UID, GID, and PID of the connecting process from its process table. A non-privileged client cannot lie about these values. This makes it suitable for authentication in local IPC systems like D-Bus, where a service needs to verify that a connecting process really is who it claims to be.

Q5. What socket flags does an accepted socket inherit from the listening socket on Linux?

Answer: On Linux, the accepted socket inherits socket options set via setsockopt() (e.g. SO_RCVBUF, SO_SNDBUF). It does NOT inherit open file status flags like O_NONBLOCK (set via fcntl()) or file descriptor flags like FD_CLOEXEC. If you need the accepted socket to be non-blocking, use accept4(lfd, NULL, NULL, SOCK_NONBLOCK) instead of accept() followed by fcntl(), which is safer in multi-threaded servers because it is atomic.

Q6. Why might a real-time video streaming application prefer UDP over TCP?

Answer: Because TCP’s retransmission makes stale data worse, not better. If a video frame is lost, TCP retransmits it. By the time the retransmitted frame arrives, it is too late to display it — the player is already past that point in the stream. Instead of showing slightly distorted video (which the player could handle by interpolating), TCP forces the player to wait for the old frame, causing a freeze or rebuffering. With UDP, the application can simply skip the lost frame and continue with the next one, resulting in a momentary glitch rather than a freeze. This is why all major video streaming protocols (WebRTC, RTSP/RTP, HLS live) use UDP at the transport layer.

Q7. What is head-of-line blocking in TCP and how does SCTP avoid it?

Answer: Head-of-line blocking occurs in TCP when a packet is lost. TCP requires all packets to be delivered in order, so even if later packets have arrived, the receiver cannot pass them to the application until the lost packet is retransmitted and received. All data “behind” the lost packet is blocked. SCTP avoids this by supporting multiple independent streams in one association. Each stream has its own sequence space, so a lost packet in stream 1 only blocks stream 1 — streams 2, 3, etc. continue to deliver data to the application without waiting.

Leave a Reply

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