SO_PEEK_OFF Gives UNIX Sockets a Stateful Peek Cursor
A normal recv(..., MSG_PEEK) examines data at the front of a socket receive queue without removing it. On Linux UNIX-domain sockets, SO_PEEK_OFF can replace that repeated front-of-queue view with a cursor that advances after each peek.
The cursor is socket state. Peeking moves it forward, while consuming bytes from the front of the queue moves it backward by the amount removed. That interaction lets a process inspect successive queued regions without consuming them, while keeping the peek position tied to the remaining queue.
A nonnegative offset changes MSG_PEEK semantics
New sockets start with a peek offset of -1. A negative value preserves the conventional behavior: each MSG_PEEK receive starts at the front of queued data.
Setting SO_PEEK_OFF to zero or a positive byte offset activates the cursor. The next MSG_PEEK begins at that offset, and a successful peek advances the stored offset by the number of bytes returned.
int off = 4;
if (setsockopt(fd, SOL_SOCKET, SO_PEEK_OFF,
&off, sizeof(off)) == -1) {
/* handle error */
}If the receive queue contains:
aabbccddeeffan offset of four places the first peek at cc. Peeking two bytes advances the cursor from 4 to 6. A second two-byte peek returns dd and advances it to 8.
This differs from repeatedly calling recv() with MSG_PEEK and no configured offset. In the conventional mode, neither call removes data, so both calls begin at the same queue head.
Ordinary receives keep the cursor relative to queued data
The cursor is not an absolute lifetime byte count. Linux adjusts it when data is consumed from the front through a receive operation that does not use MSG_PEEK.
Continuing the previous state, the cursor is 8 after two peeks. If an ordinary receive removes aa, two bytes disappear from the queue head and the cursor falls to 6. The next two-byte peek returns ee.
initial queue aabbccddeeff cursor 4
peek 2 -> cc cursor 6
peek 2 -> dd cursor 8
receive 2 -> aa cursor 6
remaining queue bbccddeeff
peek 2 -> ee cursor 8The adjustment preserves the cursor’s logical position relative to data that remains queued. Without that correction, removing bytes ahead of the cursor would shift the inspected region.
This property also means the offset cannot be treated as an application-level sequence number. Queue consumption changes its numeric value even when the logical peek position continues to refer to the same later data.
Peeking does not reserve the inspected bytes
SO_PEEK_OFF changes where MSG_PEEK reads. It does not reserve bytes, lock the receive queue, or create a transaction between inspection and later consumption.
If another thread or process can receive from the same socket, it can remove data after a peek. The kernel will adjust the shared socket cursor according to queue consumption, but application assumptions about the content inspected earlier can still become stale.
That boundary matters for parsers that inspect a header before deciding how much to consume. A stateful cursor can reduce repeated scans of the queue, but correctness still depends on ownership and synchronization around receive operations.
The option therefore supplies queue-position state, not exclusive access to that state or to the bytes behind it.
Datagram sockets expose a packet-boundary edge case
The cursor is expressed as a byte offset, while datagram sockets preserve message boundaries. A configured offset can therefore point into the middle of a queued packet.
For that case, Linux marks the returned data with MSG_TRUNC. The flag signals that the peek started within a packet rather than at its beginning.
This behavior is distinct from stream sockets, where the receive queue is consumed as a byte stream and no datagram boundary needs to be preserved. Code that applies SO_PEEK_OFF to UNIX datagram sockets must retain the distinction between cursor position and packet boundaries.
Support is narrower than the SOL_SOCKET level suggests
SO_PEEK_OFF has existed since Linux 3.4, but the documented support is currently limited to UNIX-domain sockets. Its placement at SOL_SOCKET does not make it a portable facility for TCP, UDP, or arbitrary socket families.
That scope is an architectural constraint for code that might otherwise treat socket options as family-independent. A component can use the mechanism when its IPC boundary is a supported UNIX socket, but a generic networking abstraction cannot assume equivalent behavior on Internet sockets.
The interface is also Linux-specific. Portable code needs a different state model, commonly keeping its own parse offset over data copied into an application buffer.
The useful state is the relation between cursor and queue
SO_PEEK_OFF is unusual because it turns MSG_PEEK from a stateless observation of the queue head into a stateful traversal mechanism. The kernel tracks a byte cursor, advances it after peeks, and compensates when earlier queued bytes are consumed.
That state remains deliberately narrow. It does not consume data, preserve inspected bytes against concurrent receivers, erase datagram boundaries, or extend beyond the socket families Linux documents as supporting it.
The resulting contract is precise: on a supported UNIX socket, a nonnegative peek offset selects the next queued byte for MSG_PEEK, successful peeks advance that offset, and ordinary front-of-queue consumption shifts it backward to preserve its relative position.