Why Do We Need Socket Options?
A socket is not just a communication pipe โ it has many tuneable behaviours. How long does it wait before timing out? Should it reuse a port that is still in TIME_WAIT? Can it send broadcast packets? All of these behaviours are controlled through socket options.
Linux provides two system calls to work with socket options: setsockopt() to change an option and getsockopt() to read the current value of an option. Together, they give you fine-grained control over how your socket behaves.
Both calls are declared in <sys/socket.h>:
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
/* Both return 0 on success, -1 on error */
The level argument selects which layer of the protocol stack the option targets. Think of the TCP/IP stack as layers:
Most common options in this book use SOL_SOCKET, meaning they apply at the socket API level rather than at a specific protocol layer. When you need TCP-specific options (like TCP_NODELAY), you use IPPROTO_TCP.
/* Common levels and their options */
/* SOL_SOCKET โ socket-level options */
SO_REUSEADDR /* allow reuse of local address */
SO_KEEPALIVE /* send keep-alive probes */
SO_TYPE /* get socket type (read-only) */
SO_SNDBUF /* send buffer size */
SO_RCVBUF /* receive buffer size */
SO_LINGER /* linger on close if data pending */
/* IPPROTO_TCP โ TCP-level options */
TCP_NODELAY /* disable Nagle algorithm */
TCP_KEEPIDLE /* idle time before keepalive probe */
/* IPPROTO_IP โ IP-level options */
IP_TTL /* IP time-to-live field value */
This is a common source of bugs. Pay close attention:
You pass the size directly as an integer. The kernel only reads from the buffer pointed to by optval.
socklen_t optlen = sizeof(int);
setsockopt(sfd, SOL_SOCKET,
SO_REUSEADDR,
&val, optlen); /* value, not pointer */
You initialize optlen to your buffer size. After the call, the kernel writes back the actual number of bytes written.
socklen_t optlen = sizeof(int);
getsockopt(sfd, SOL_SOCKET,
SO_TYPE,
&optval,
&optlen); /* pointer โ kernel updates it */
If you pass optlen by value to getsockopt (forgetting the &), it compiles but gives wrong results โ a very subtle bug.
SO_TYPE is a read-only option. You can only use getsockopt with it โ trying to set it with setsockopt will fail. It returns the socket type: SOCK_STREAM, SOCK_DGRAM, etc.
This is useful when a program inherits a socket file descriptor from its parent (via exec() or from a super-server like inetd) and does not know what type of socket it received.
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
void print_socket_type(int sfd)
{
int optval;
socklen_t optlen = sizeof(optval);
if (getsockopt(sfd, SOL_SOCKET, SO_TYPE, &optval, &optlen) == -1) {
perror("getsockopt SO_TYPE");
exit(EXIT_FAILURE);
}
switch (optval) {
case SOCK_STREAM:
printf("Socket type: SOCK_STREAM (TCP)\n");
break;
case SOCK_DGRAM:
printf("Socket type: SOCK_DGRAM (UDP)\n");
break;
case SOCK_RAW:
printf("Socket type: SOCK_RAW\n");
break;
default:
printf("Socket type: unknown (%d)\n", optval);
}
}
int main(void)
{
int tcp_sfd = socket(AF_INET, SOCK_STREAM, 0);
int udp_sfd = socket(AF_INET, SOCK_DGRAM, 0);
if (tcp_sfd == -1 || udp_sfd == -1) {
perror("socket"); exit(EXIT_FAILURE);
}
printf("TCP socket fd %d: ", tcp_sfd);
print_socket_type(tcp_sfd);
printf("UDP socket fd %d: ", udp_sfd);
print_socket_type(udp_sfd);
close(tcp_sfd);
close(udp_sfd);
return 0;
}
Expected output:
TCP socket fd 3: Socket type: SOCK_STREAM (TCP)
UDP socket fd 4: Socket type: SOCK_DGRAM (UDP)
Two important inheritance behaviours to know:
When accept() creates a new connected socket, that new socket inherits the settable socket options of the listening socket. So if you set SO_REUSEADDR on the listening socket, all accepted sockets also have it.
Socket options belong to the open file description (the underlying kernel object), not to the file descriptor number. So when you dup() a socket fd, or after fork(), the duplicated descriptors all share the same socket options.
/* Demonstrating that dup'd fds share socket options */
int sfd = socket(AF_INET, SOCK_STREAM, 0);
int sfd2 = dup(sfd); /* sfd2 points to same open file description */
int val = 1;
/* Set option on sfd โ sfd2 sees the same change! */
setsockopt(sfd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val));
int optval;
socklen_t optlen = sizeof(optval);
getsockopt(sfd2, SOL_SOCKET, SO_KEEPALIVE, &optval, &optlen);
/* optval == 1 here even though we never called setsockopt on sfd2 */
| Option | Level | Type | Purpose |
|---|---|---|---|
SO_REUSEADDR |
SOL_SOCKET | int (bool) | Allow bind to port in TIME_WAIT |
SO_TYPE |
SOL_SOCKET | int (read-only) | Get socket type (STREAM/DGRAM) |
SO_KEEPALIVE |
SOL_SOCKET | int (bool) | Enable TCP keepalive probes |
SO_SNDBUF |
SOL_SOCKET | int | Set send buffer size in bytes |
SO_RCVBUF |
SOL_SOCKET | int | Set receive buffer size in bytes |
SO_LINGER |
SOL_SOCKET | struct linger | Wait for unsent data on close() |
TCP_NODELAY |
IPPROTO_TCP | int (bool) | Disable Nagle algorithm for low latency |
IP_TTL |
IPPROTO_IP | int | Set IP packet time-to-live value |
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
void get_int_option(int sfd, int level, int optname, const char *name)
{
int optval;
socklen_t optlen = sizeof(optval);
if (getsockopt(sfd, level, optname, &optval, &optlen) == -1) {
perror(name);
return;
}
printf(" %-20s = %d\n", name, optval);
}
int main(void)
{
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if (sfd == -1) { perror("socket"); exit(EXIT_FAILURE); }
printf("Socket options for fd %d:\n", sfd);
get_int_option(sfd, SOL_SOCKET, SO_TYPE, "SO_TYPE");
get_int_option(sfd, SOL_SOCKET, SO_KEEPALIVE, "SO_KEEPALIVE");
get_int_option(sfd, SOL_SOCKET, SO_SNDBUF, "SO_SNDBUF");
get_int_option(sfd, SOL_SOCKET, SO_RCVBUF, "SO_RCVBUF");
get_int_option(sfd, IPPROTO_TCP, TCP_NODELAY, "TCP_NODELAY");
/* Now enable TCP_NODELAY and verify */
int val = 1;
setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val));
printf("\nAfter enabling TCP_NODELAY:\n");
get_int_option(sfd, IPPROTO_TCP, TCP_NODELAY, "TCP_NODELAY");
close(sfd);
return 0;
}
Sample output:
Socket options for fd 3:
SO_TYPE = 1 (1 = SOCK_STREAM)
SO_KEEPALIVE = 0 (disabled by default)
SO_SNDBUF = 87380 (bytes, kernel may double this)
SO_RCVBUF = 174760
TCP_NODELAY = 0 (Nagle on by default)
After enabling TCP_NODELAY:
TCP_NODELAY = 1
Q1. What is the difference between setsockopt() and getsockopt()?
setsockopt() sets the value of a socket option. getsockopt() retrieves the current value. Both take the same parameters but optlen is passed by value in set and by pointer (value-result) in get.
Q2. What does the level parameter mean in setsockopt()?
It selects which protocol layer owns the option. SOL_SOCKET is for options that apply at the socket API level. IPPROTO_TCP is for TCP-specific options. IPPROTO_IP is for IP-level options.
Q3. Why is optlen a pointer in getsockopt() but a value in setsockopt()?
Because getsockopt() uses optlen as a value-result argument. You initialize it with the size of your buffer before the call. After the call, the kernel writes back the actual number of bytes it placed in the buffer. This lets you know if the option data was truncated.
Q4. What is SO_TYPE and when would you use it?
SO_TYPE is a read-only socket option that returns the socket type (SOCK_STREAM, SOCK_DGRAM, etc.). It is useful when a process inherits a socket from another (for example, a daemon spawned by inetd) and needs to determine what kind of socket it received.
Q5. Do socket options persist across accept()?
Yes. A socket created by accept() inherits the settable socket options from the listening socket. This means you only need to set options on the listening socket before entering the accept loop.
Q6. If I dup() a socket file descriptor and change an option on the duplicate, does the original see the change?
Yes. Socket options are associated with the underlying open file description, not with a specific file descriptor number. After dup() or fork(), both descriptors point to the same open file description, so any option change via one descriptor is visible through the other.
Q7. What does TCP_NODELAY do and when would you use it?
TCP_NODELAY disables the Nagle algorithm. By default, TCP delays small writes to coalesce them into larger segments. Disabling this reduces latency but may increase the number of packets. It is useful in real-time applications like games, financial trading systems, or SSH where every keystroke matters.
