sock_diag(7)
๐ sock_diag ๐ โ it's the sock_diag(7) manual, Miscellaneous Information Manual energy
okay bestie, sit DOWN because today we're learning how to ask the kernel "hey girl, what sockets do you have open?" ๐ซถ This is sock_diag, and it's giving detective vibes, it's giving nosy bestie, it's giving tell me everything.
โจ NAME โจ
sock_diag โ obtaining information about sockets ๐
โจ SYNOPSIS โจ
#include <sys/socket.h>
#include <linux/sock_diag.h>
#include <linux/unix_diag.h> /* for UNIX domain sockets */
#include <linux/inet_diag.h> /* for IPv4 and IPv6 sockets */
diag_socket = socket(AF_NETLINK, socket_type, NETLINK_SOCK_DIAG);
โจ DESCRIPTION โจ
So the sock_diag netlink subsystem? It's literally the hookup for getting info about sockets from the kernel โ and not just one address family, like, various ones โจ. You can use it to snoop on one single socket, or you can ask for a whole list of sockets. Both. She does both. ๐
When you send a request, you get to say extra stuff you wanna know about the socket โ like memory info, or address-family-specific tea ๐ต.
And when you're requesting a whole list of sockets? You can hand the kernel filters and it will apply them, picking out just a subset of sockets to report back. For now, the only filtering ability is by state (connected, listening, and so on). That's it. That's the filter. Don't get greedy ๐.
Important lil note:sock_diagonly reports sockets that have a name โ meaning either sockets bound explicitly withbind(2), or sockets that got automatically bound to an address (e.g., byconnect(2)). It's the exact same set of sockets you'd see via/proc/net/unix,/proc/net/tcp,/proc/net/udp, and so on. Same crew, different door ๐ช
๐ธ Request ๐ธ
The request kicks off with a struct nlmsghdr header (described over in netlink(7)) where the nlmsg_type field is set to SOCK_DIAG_BY_FAMILY. Then right after that comes an address-family-specific header, which starts with a common part that literally every address family shares:
struct sock_diag_req {
__u8 sdiag_family;
__u8 sdiag_protocol;
};
The fields of this structure are as follows, bestie:
sdiag_familyโ an address family. Set it to the appropriateAF_*constant.sdiag_protocolโ depends onsdiag_family. Set it to the appropriateIPPROTO_*constant forAF_INETandAF_INET6, and to0otherwise.
If the nlmsg_flags field of the struct nlmsghdr header has the NLM_F_DUMP flag set, that means you're requesting a list of sockets โจ. Otherwise? It's a query about one single individual socket. Just the one. Main character socket ๐
๐ธ Response ๐ธ
The response starts with a struct nlmsghdr header, and then it's followed by an array of objects specific to the address family. You access that array with the standard NLMSG_* macros from the netlink(3) API.
Each object is the NLA (netlink attributes) list, and you access that with the RTA_* macros from the rtnetlink(3) API. Nested cuteness. Layers on layers ๐ง
๐ธ UNIX domain sockets ๐ธ
For UNIX domain sockets, the request is represented in this structure:
struct unix_diag_req {
__u8 sdiag_family;
__u8 sdiag_protocol;
__u16 pad;
__u32 udiag_states;
__u32 udiag_ino;
__u32 udiag_show;
__u32 udiag_cookie[2];
};
The fields of this structure are as follows:
sdiag_familyโ the address family; set it toAF_UNIX.sdiag_protocolpadโ these fields should be set to0.udiag_statesโ this is a bit mask that defines a filter of socket states. Only the sockets whose states are in this mask get reported. It's ignored when you're querying for an individual socket. The supported values are:1 << TCP_ESTABLISHED1 << TCP_LISTEN
udiag_inoโ this is an inode number, used when querying for an individual socket. It's ignored when querying for a list of sockets.udiag_showโ this is a set of flags defining what kind of information to report. Each requested kind of info gets reported back as a netlink attribute, like this:UDIAG_SHOW_NAMEโ the attribute reported in answer to this request isUNIX_DIAG_NAME. The payload associated with this attribute is the pathname to which the socket was bound (a sequence of bytes up toUNIX_PATH_MAXsize).UDIAG_SHOW_VFSโ the attribute reported in answer to this request isUNIX_DIAG_VFS. The payload associated with this attribute is represented in the following structure:
struct unix_diag_vfs {
__u32 udiag_vfs_dev;
__u32 udiag_vfs_ino;
};
The fields of this structure are as follows:
udiag_vfs_devโ the device number of the corresponding on-disk socket inode.udiag_vfs_inoโ the inode number of the corresponding on-disk socket inode.
UDIAG_SHOW_PEERโ the attribute reported in answer to this request isUNIX_DIAG_PEER. The payload associated with this attribute is a__u32value, which is the peer's inode number. This attribute is reported for connected sockets only.UDIAG_SHOW_ICONSโ the attribute reported in answer to this request isUNIX_DIAG_ICONS. The payload associated with this attribute is an array of__u32values, which are inode numbers of sockets that have passed theconnect(2)call but haven't been processed withaccept(2)yet. This attribute is reported for listening sockets only. (Yes it's called ICONS. No we don't know why. Slay.)UDIAG_SHOW_RQLENโ the attribute reported in answer to this request isUNIX_DIAG_RQLEN. The payload associated with this attribute is represented in the following structure:
struct unix_diag_rqlen {
__u32 udiag_rqueue;
__u32 udiag_wqueue;
};
The fields of this structure are as follows:
udiag_rqueueโ for listening sockets: the number of pending connections. The size of the array associated with theUNIX_DIAG_ICONSresponse attribute is equal to this value.
For established sockets: the amount of data in the incoming queue.
udiag_wqueueโ for listening sockets: the backlog size, which equals the value passed as the second argument tolisten(2).
For established sockets: the amount of memory available for sending.
UDIAG_SHOW_MEMINFOโ the attribute reported in answer to this request isUNIX_DIAG_MEMINFO. The payload associated with this attribute is an array of__u32values, described below in the subsection "Socket memory information".
The following attribute is reported back without any specific request:
UNIX_DIAG_SHUTDOWNโ the payload associated with this attribute is a__u8value which represents bits ofshutdown(2)state.
udiag_cookieโ this is an array of opaque identifiers that could be used along withudiag_inoto specify an individual socket. It's ignored when querying for a list of sockets, and also when all its elements are set to-1.
The response to a query for UNIX domain sockets is represented as an array of
struct unix_diag_msg {
__u8 udiag_family;
__u8 udiag_type;
__u8 udiag_state;
__u8 pad;
__u32 udiag_ino;
__u32 udiag_cookie[2];
};
followed by netlink attributes.
The fields of this structure are as follows:
udiag_familyโ this field has the same meaning as instruct unix_diag_req.udiag_typeโ this is set to one ofSOCK_PACKET,SOCK_STREAM, orSOCK_SEQPACKET.udiag_stateโ this is set to one ofTCP_LISTENorTCP_ESTABLISHED.padโ this field is set to0.udiag_inoโ this is the socket inode number.udiag_cookieโ this is an array of opaque identifiers that could be used in subsequent queries.
๐ธ IPv4 and IPv6 sockets ๐ธ
For IPv4 and IPv6 sockets, the request is represented in the following structure:
struct inet_diag_req_v2 {
__u8 sdiag_family;
__u8 sdiag_protocol;
__u8 idiag_ext;
__u8 pad;
__u32 idiag_states;
struct inet_diag_sockid id;
};
where struct inet_diag_sockid is defined as follows:
struct inet_diag_sockid {
__be16 idiag_sport;
__be16 idiag_dport;
__be32 idiag_src[4];
__be32 idiag_dst[4];
__u32 idiag_if;
__u32 idiag_cookie[2];
};
The fields of struct inet_diag_req_v2 are as follows:
sdiag_familyโ this should be set to eitherAF_INETorAF_INET6, for IPv4 or IPv6 sockets respectively.sdiag_protocolโ this should be set to one ofIPPROTO_TCP,IPPROTO_UDP, orIPPROTO_UDPLITE.idiag_extโ this is a set of flags defining what kind of extended information to report. Each requested kind of info gets reported back as a netlink attribute, like this:INET_DIAG_TOSโ the payload associated with this attribute is a__u8value, which is the TOS of the socket.INET_DIAG_TCLASSโ the payload associated with this attribute is a__u8value, which is the TClass of the socket. IPv6 sockets only. ForLISTENandCLOSEsockets, this is followed by theINET_DIAG_SKV6ONLYattribute with an associated__u8payload value meaning whether the socket is IPv6-only or not.INET_DIAG_MEMINFOโ the payload associated with this attribute is represented in the following structure:
struct inet_diag_meminfo {
__u32 idiag_rmem;
__u32 idiag_wmem;
__u32 idiag_fmem;
__u32 idiag_tmem;
};
The fields of this structure are as follows:
idiag_rmemโ the amount of data in the receive queue.idiag_wmemโ the amount of data that is queued by TCP but not yet sent.idiag_fmemโ the amount of memory scheduled for future use (TCP only).
idiag_tmemโ the amount of data chillin' in the send queue ๐INET_DIAG_SKMEMINFOโ the payload that rides along with this attribute is an array of__u32values, and bestie, every single one of them is spelled out down below in the "Socket memory information" subsection ๐INET_DIAG_INFOโ the payload that rides along with this attribute is specific to the address family. If we're talking TCP sockets, it's an object of typestruct tcp_info.INET_DIAG_CONGโ the payload that rides along with this attribute is a string describing the congestion control algorithm being used. TCP sockets only, that's the rule ๐padโ this should be set to0. That's it, that's the whole assignment. Set it to0โจidiag_statesโ a bit mask that filters which socket states you get back ๐ฏ Only the sockets whose states land inside this mask get reported. It's ignored when you're querying a single individual socket.idโ a socket ID object ๐ชช It shows up in dump requests, in queries about individual sockets, and it gets reported back in every single response. And here's the thing: unlike UNIX domain sockets, IPv4 and IPv6 sockets get identified using addresses and ports. All of these values are in network byte order, so don't get caught out ๐ญ
The fields of struct inet_diag_sockid are as follows:
idiag_sportโ the source port.idiag_dportโ the destination port.idiag_srcโ the source address.idiag_dstโ the destination address.idiag_ifโ the interface number the socket is bound to.idiag_cookieโ an array of opaque identifiers ๐ช You can use these together with the other fields of this structure to pin down one specific socket. But they get ignored when you're querying for a whole list of sockets, and also ignored whenever all of their elements are set to-1.
The response to a query for IPv4 or IPv6 sockets is represented as an array of
struct inet_diag_msg {
__u8 idiag_family;
__u8 idiag_state;
__u8 idiag_timer;
__u8 idiag_retrans;
struct inet_diag_sockid id;
__u32 idiag_expires;
__u32 idiag_rqueue;
__u32 idiag_wqueue;
__u32 idiag_uid;
__u32 idiag_inode;
};
followed by netlink attributes ๐ซ
The fields of this structure are as follows:
idiag_familyโ literally the same field as instruct inet_diag_req_v2.idiag_stateโ this is the socket state, same deal as instruct inet_diag_req_v2.idiag_timerโ for TCP sockets, this field tells you which kind of timer is active on the socket right now. It's set to one of these constants:0โ no timer is active1โ a retransmit timer2โ a keep-alive timer3โ aTIME_WAITtimer4โ a zero window probe timer
For non-TCP sockets, this field is set to 0.
idiag_retransโ foridiag_timervalues1,2, and4, this field holds the number of retransmits. For any otheridiag_timervalue, this field is set to0.idiag_expiresโ for TCP sockets that have an active timer, this field describes its expiration time in milliseconds. For every other socket, this field is set to0.idiag_rqueueโ for listening sockets: the number of pending connections. For other sockets: the amount of data in the incoming queue.idiag_wqueueโ for listening sockets: the backlog size. For other sockets: the amount of memory available for sending.idiag_uidโ this is the socket owner UID.idiag_inodeโ this is the socket inode number.
๐ง Socket memory information
The payload that comes with the UNIX_DIAG_MEMINFO and INET_DIAG_SKMEMINFO netlink attributes is an array of the following __u32 values:
SK_MEMINFO_RMEM_ALLOCโ the amount of data in the receive queue.SK_MEMINFO_RCVBUFโ the receive socket buffer as set bySO_RCVBUF.SK_MEMINFO_WMEM_ALLOCโ the amount of data in the send queue.SK_MEMINFO_SNDBUFโ the send socket buffer as set bySO_SNDBUF.SK_MEMINFO_FWD_ALLOCโ the amount of memory scheduled for future use (TCP only).SK_MEMINFO_WMEM_QUEUEDโ the amount of data queued by TCP, but not yet sent.SK_MEMINFO_OPTMEMโ the amount of memory allocated for the socket's service needs (e.g., socket filter).SK_MEMINFO_BACKLOGโ the amount of packets in the backlog (not yet processed).
โจ VERSIONS โจ
History lesson time, bestie ๐ NETLINK_INET_DIAG was introduced in Linux 2.6.14, and back then it only supported AF_INET and AF_INET6 sockets. Then in Linux 3.3, it got renamed to NETLINK_SOCK_DIAG and extended to support AF_UNIX sockets too. Glow-up behavior honestly ๐
UNIX_DIAG_MEMINFO and INET_DIAG_SKMEMINFO were introduced in Linux 3.6.
๐ STANDARDS ๐
Linux. That's the whole section. Iconic brevity ๐
๐ EXAMPLES ๐
okay so this little program right here prints the inode number, the peer's inode number, and the name of every UNIX domain socket hanging out in the current namespace ๐ซถ
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/sock_diag.h>
#include <linux/unix_diag.h>
static int
send_query(int fd)
{
struct sockaddr_nl nladdr = {
.nl_family = AF_NETLINK
};
struct
{
struct nlmsghdr nlh;
struct unix_diag_req udr;
} req = {
.nlh = {
.nlmsg_len = sizeof(req),
.nlmsg_type = SOCK_DIAG_BY_FAMILY,
.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP
},
.udr = {
.sdiag_family = AF_UNIX,
.udiag_states = -1,
.udiag_show = UDIAG_SHOW_NAME | UDIAG_SHOW_PEER
}
};
struct iovec iov = {
.iov_base = &req,
.iov_len = sizeof(req)
};
struct msghdr msg = {
.msg_name = &nladdr,
.msg_namelen = sizeof(nladdr),
.msg_iov = &iov,
.msg_iovlen = 1
};
for (;;) {
if (sendmsg(fd, &msg, 0) < 0) {
if (errno == EINTR)
continue;
perror("sendmsg");
return -1;
}
return 0;
}
}
static int
print_diag(const struct unix_diag_msg *diag, unsigned int size)
{
if (size < NLMSG_LENGTH(sizeof(*diag))) {
fputs("short response\n", stderr);
return -1;
}
if (diag->udiag_family != AF_UNIX) {
fprintf(stderr, "unexpected family %u\n", diag->udiag_family);
return -1;
}
unsigned int rta_len = size - NLMSG_LENGTH(sizeof(*diag));
unsigned int peer = 0;
size_t path_len = 0;
char path[sizeof(((struct sockaddr_un *) 0)->sun_path) + 1];
for (struct rtattr *attr = (struct rtattr *) (diag + 1);
RTA_OK(attr, rta_len); attr = RTA_NEXT(attr, rta_len)) {
switch (attr->rta_type) {
case UNIX_DIAG_NAME:
if (!path_len) {
path_len = RTA_PAYLOAD(attr);
if (path_len > sizeof(path) - 1)
path_len = sizeof(path) - 1;
memcpy(path, RTA_DATA(attr), path_len);
path[path_len] = '\0';
}
break;
case UNIX_DIAG_PEER:
if (RTA_PAYLOAD(attr) >= sizeof(peer))
peer = *(unsigned int *) RTA_DATA(attr);
break;
}
}
printf("inode=%u", diag->udiag_ino);
if (peer)
printf(", peer=%u", peer);
if (path_len)
printf(", name=%s%s", *path ? "" : "@",
*path ? path : path + 1);
putchar('\n');
return 0;
}
static int
receive_responses(int fd)
{
long buf[8192 / sizeof(long)];
struct sockaddr_nl nladdr;
struct iovec iov = {
.iov_base = buf,
.iov_len = sizeof(buf)
};
int flags = 0;
for (;;) {
struct msghdr msg = {
.msg_name = &nladdr,
.msg_namelen = sizeof(nladdr),
.msg_iov = &iov,
.msg_iovlen = 1
};
ssize_t ret = recvmsg(fd, &msg, flags);
if (ret < 0) {
if (errno == EINTR)
continue;
perror("recvmsg");
return -1;
}
if (ret == 0)
return 0;
if (nladdr.nl_family != AF_NETLINK) {
fputs("!AF_NETLINK\n", stderr);
return -1;
}
const struct nlmsghdr *h = (struct nlmsghdr *) buf;
if (!NLMSG_OK(h, ret)) {
fputs("!NLMSG_OK\n", stderr);
return -1;
}
for (; NLMSG_OK(h, ret); h = NLMSG_NEXT(h, ret)) {
if (h->nlmsg_type == NLMSG_DONE)
return 0;
if (h->nlmsg_type == NLMSG_ERROR) {
const struct nlmsgerr *err = NLMSG_DATA(h);
if (h->nlmsg_len < NLMSG_LENGTH(sizeof(*err))) {
fputs("NLMSG_ERROR\n", stderr);
} else {
errno = -err->error;
perror("NLMSG_ERROR");
}
return -1;
}
if (h->nlmsg_type != SOCK_DIAG_BY_FAMILY) {
fprintf(stderr, "unexpected nlmsg_type %u\n",
(unsigned) h->nlmsg_type);
return -1;
}
if (print_diag(NLMSG_DATA(h), h->nlmsg_len))
return -1;
}
}
}
int
main(void)
{
int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG);
if (fd < 0) {
perror("socket");
return 1;
}
int ret = send_query(fd) || receive_responses(fd);
close(fd);
return ret;
}
๐ฆ SEE ALSO ๐ฆ
and that's the whole page, bestie ๐ฅน this is Linux man-pages 6.20, dated 2026-02-08 โ go forth and diag those sockets, you absolute legend ๐๐
๐ฆ where this page comes from
- arch rolling: man-pages 6.20-1 ยท BSD-2-Clause AND BSD-3-Clause AND BSD-4.3TAHOE AND BSD-4-Clause-UC AND GPL-1.0-or-later AND GPL-2.0-only AND GPL-2.0-or-later AND GPL-3.0-or-later AND LGPL-3.0-linking-exception AND LGPL-3.0-or-later AND LicenseRef-Public-Domain AND LicenseRef-UltraPermissive AND Linux-man-pages-1-para AND Linux-man-pages-copyleft AND Linux-man-pages-copyleft-2-para AND Linux-man-pages-copyleft-var AND MIT AND Spencer-94
- nixpkgs unstable: man-pages 6.20
- ๐ the page's license: GPL-2.0-or-later
- Copyright 2016, Pavel Emelyanov <xemul@virtuozzo.com>
- Copyright 2016, Dmitry V. Levin <ldv@altlinux.org>
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms