0

I'm trying to write a program (in C) which, as one of its features, walks the procfs to find open sockets and ascertain things like the destination/source ports/addresses (similar to what netstat and lsof do). However, I'm not sure which system call to use once I find a socket file. For example, suppose I've called readlink on /proc/123/fd/4 and have gotten back socket:[56789]. What can I do with this information, using the system API alone, to get the socket's details?

I've tried running strace on netstat but it's not clear what's going on. I see a read on /proc/123/fdinfo/4 but I don't understand what that's all about.

For example, the fdinfo file for one open connection (TCP connection to 127.0.0.1:5000) shows

pos:    0
flags:  04002
mnt_id: 9
3
  • Looking at the source code of netstat might show how it's done there.
    – Bodo
    Jan 26, 2022 at 18:16
  • 1
    If you were to cat out /proc/123/fdinfo/4, you may get a better idea of what it is all about.
    – Bib
    Jan 26, 2022 at 18:25
  • 1
    Related: Who's got the other end of this unix socketpair?. On Linux, there are some old style /proc/net/unix, /proc/net/tcp... API and there are new netlink ones. Look at the source of ss or lsof for details. Jan 26, 2022 at 20:57

2 Answers 2

2

/proc/pid/fdinfo/fd gives information about attributes of the file descriptor and the open file description behind it and is documented in the proc(5) man page.

It doesn't have socket-specific information.

Historically on Linux, information about sockets could be obtained from text files in /proc/net, one for each address family and protocol (like /proc/net/unix, /proc/net/udp6...).

But there is now an alternative netlink API that is more appropriate for programatic use and more reliable (in particular /proc/net/unix cannot be parsed reliably as it breaks with socket files with newline characters) and featureful.

That is documented in the netlink(7) man page, and specifically for the socket diagnostics information in sock_diag(7) (renamed from inet_diag and extended to other families in 2011 itself renamed and extended from tcpdiag in 2005 itself added in 2.5.2.1 in 2002) . See also info at kernel.org:

That is itself a socket-based API used by applications to talk to the kernel. Like for other sockets, you use the socket() system call to create the socket (with AF_NETLINK as the address family), some setsockopt()s to tune it and sendmsg()/recvmsg() to send/receive the information.

That's what ss (from iproute2) uses. While netstat (from net-tools), lsfd (from util-linux) seem to still be using the /proc/net/... files interface. lsof can also use it on Linux for its +E option.

Newer versions of strace can decode the messages sent and received on those sockets, so you can see it in action with ss for instance with:

$ strace -qqe network ss -lt
socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_SOCK_DIAG) = 3
setsockopt(3, SOL_SOCKET, SO_SNDBUF, [32768], 4) = 0
setsockopt(3, SOL_SOCKET, SO_RCVBUF, [1048576], 4) = 0
setsockopt(3, SOL_NETLINK, NETLINK_EXT_ACK, [1], 4) = 0
bind(3, {sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, 12) = 0
getsockname(3, {sa_family=AF_NETLINK, nl_pid=9352, nl_groups=00000000}, [12]) = 0
sendmsg(3, {msg_name={sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, msg_namelen=12, msg_iov=[{iov_base=[{nlmsg_len=72, nlmsg_type=SOCK_DIAG_BY_FAMILY, nlmsg_flags=NLM_F_REQUEST|NLM_F_DUMP, nlmsg_seq=123456, nlmsg_pid=0}, {sdiag_family=AF_INET, sdiag_protocol=IPPROTO_TCP, idiag_ext=0, idiag_states=1<<TCP_CLOSE|1<<TCP_LISTEN, id={idiag_sport=htons(0), idiag_dport=htons(0), idiag_src=inet_addr("0.0.0.0"), idiag_dst=inet_addr("0.0.0.0"), idiag_if=0, idiag_cookie=[0, 0]}}], iov_len=72}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = 72
recvmsg(3, {msg_name={sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, msg_namelen=12, msg_iov=[{iov_base=NULL, iov_len=0}], msg_iovlen=1, msg_controllen=0, msg_flags=MSG_TRUNC}, MSG_PEEK|MSG_TRUNC) = 812
recvmsg(3, {msg_name={sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, msg_namelen=12, msg_iov=[{iov_base=[[{nlmsg_len=116, nlmsg_type=SOCK_DIAG_BY_FAMILY, nlmsg_flags=NLM_F_MULTI, nlmsg_seq=123456, nlmsg_pid=9352}, {idiag_family=AF_INET, idiag_state=TCP_LISTEN, idiag_timer=0, idiag_retrans=0, id={idiag_sport=htons(4330), idiag_dport=htons(0), idiag_src=inet_addr("0.0.0.0"), idiag_dst=inet_addr("0.0.0.0"), idiag_if=0, idiag_cookie=[8331, 0]}, idiag_expires=0, idiag_rqueue=0, idiag_wqueue=5, idiag_uid=997, idiag_inode=18774}, [[{nla_len=5, nla_type=INET_DIAG_SHUTDOWN}, 0], [{nla_len=12, nla_type=INET_DIAG_CGROUP_ID}, 5094], [{nla_len=6, nla_type=INET_DIAG_SOCKOPT}, {is_icsk=1, mc_loop=1, mc_all=1}]]], [{nlmsg_len=116, nlmsg_type=SOCK_DIAG_BY_FAMILY, nlmsg_flags=NLM_F_MULTI, nlmsg_seq=123456, nlmsg_pid=9352}, {idiag_family=AF_INET, idiag_state=TCP_LISTEN, idiag_timer=0, idiag_retrans=0, id={idiag_sport=htons(3306), idiag_dport=htons(0), idiag_src=inet_addr("127.0.0.1"), idiag_dst=inet_addr("0.0.0.0"), idiag_if=0, idiag_cookie=[8332, 0]}, idiag_expires=0, idiag_rqueue=0, idiag_wqueue=80, idiag_uid=121, idiag_inode=4823}, [[{nla_len=5, nla_type=INET_DIAG_SHUTDOWN}, 0], [{nla_len=12, nla_type=INET_DIAG_CGROUP_ID}, 3733], [{nla_len=6, nla_type=INET_DIAG_SOCKOPT}, {is_icsk=1, freebind=1, mc_loop=1, mc_all=1}]]], [{nlmsg_len=116, nlmsg_type=SOCK_DIAG_BY_FAMILY, nlmsg_flags=NLM_F_MULTI, nlmsg_seq=123456, nlmsg_pid=9352}, {idiag_family=AF_INET, idiag_state=TCP_LISTEN, idiag_timer=0, idiag_retrans=0, id={idiag_sport=htons(22), idiag_dport=htons(0), idiag_src=inet_addr("0.0.0.0"), idiag_dst=inet_addr("0.0.0.0"), idiag_if=0, idiag_cookie=[8333, 0]}, idiag_expires=0, idiag_rqueue=0, idiag_wqueue=128, idiag_uid=0, idiag_inode=2707}, [[{nla_len=5, nla_type=INET_DIAG_SHUTDOWN}, 0], [{nla_len=12, nla_type=INET_DIAG_CGROUP_ID}, 3774], [{nla_len=6, nla_type=INET_DIAG_SOCKOPT}, {is_icsk=1, mc_loop=1, mc_all=1}]]], [{nlmsg_len=116, nlmsg_type=SOCK_DIAG_BY_FAMILY, nlmsg_flags=NLM_F_MULTI, nlmsg_seq=123456, nlmsg_pid=9352}, {idiag_family=AF_INET, idiag_state=TCP_LISTEN, idiag_timer=0, idiag_retrans=0, id={idiag_sport=htons(44321), idiag_dport=htons(0), idiag_src=inet_addr("0.0.0.0"), idiag_dst=inet_addr("0.0.0.0"), idiag_if=0, idiag_cookie=[8334, 0]}, idiag_expires=0, idiag_rqueue=0, idiag_wqueue=5, idiag_uid=0, idiag_inode=18553}, [[{nla_len=5, nla_type=INET_DIAG_SHUTDOWN}, 0], [{nla_len=12, nla_type=INET_DIAG_CGROUP_ID}, 5012], [{nla_len=6, nla_type=INET_DIAG_SOCKOPT}, {is_icsk=1, mc_loop=1, mc_all=1}]]], [{nlmsg_len=116, nlmsg_type=SOCK_DIAG_BY_FAMILY, nlmsg_flags=NLM_F_MULTI, nlmsg_seq=123456, nlmsg_pid=9352}, {idiag_family=AF_INET, idiag_state=TCP_LISTEN, idiag_timer=0, idiag_retrans=0, id={idiag_sport=htons(44322), idiag_dport=htons(0), idiag_src=inet_addr("0.0.0.0"), idiag_dst=inet_addr("0.0.0.0"), idiag_if=0, idiag_cookie=[8335, 0]}, idiag_expires=0, idiag_rqueue=0, idiag_wqueue=128, idiag_uid=0, idiag_inode=18613}, [[{nla_len=5, nla_type=INET_DIAG_SHUTDOWN}, 0], [{nla_len=12, nla_type=INET_DIAG_CGROUP_ID}, 5135], [{nla_len=6, nla_type=INET_DIAG_SOCKOPT}, {is_icsk=1, mc_loop=1, mc_all=1}]]], [{nlmsg_len=116, nlmsg_type=SOCK_DIAG_BY_FAMILY, nlmsg_flags=NLM_F_MULTI, nlmsg_seq=123456, nlmsg_pid=9352}, {idiag_family=AF_INET, idiag_state=TCP_LISTEN, idiag_timer=0, idiag_retrans=0, id={idiag_sport=htons(44323), idiag_dport=htons(0), idiag_src=inet_addr("0.0.0.0"), idiag_dst=inet_addr("0.0.0.0"), idiag_if=0, idiag_cookie=[8336, 0]}, idiag_expires=0, idiag_rqueue=0, idiag_wqueue=128, idiag_uid=0, idiag_inode=18614}, [[{nla_len=5, nla_type=INET_DIAG_SHUTDOWN}, 0], [{nla_len=12, nla_type=INET_DIAG_CGROUP_ID}, 5135], [{nla_len=6, nla_type=INET_DIAG_SOCKOPT}, {is_icsk=1, mc_loop=1, mc_all=1}]]], [{nlmsg_len=116, nlmsg_type=SOCK_DIAG_BY_FAMILY, nlmsg_flags=NLM_F_MULTI, nlmsg_seq=123456, nlmsg_pid=9352}, {idiag_family=AF_INET, idiag_state=TCP_LISTEN, idiag_timer=0, idiag_retrans=0, id={idiag_sport=htons(631), idiag_dport=htons(0), idiag_src=inet_addr("127.0.0.1"), idiag_dst=inet_addr("0.0.0.0"), idiag_if=0, idiag_cookie=[8337, 0]}, idiag_expires=0, idiag_rqueue=0, idiag_wqueue=4096, idiag_uid=0, idiag_inode=3811}, [[{nla_len=5, nla_type=INET_DIAG_SHUTDOWN}, 0], [{nla_len=12, nla_type=INET_DIAG_CGROUP_ID}, 3610], [{nla_len=6, nla_type=INET_DIAG_SOCKOPT}, {is_icsk=1, mc_loop=1, mc_all=1}]]]], iov_len=32768}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = 812
1

You first need to read /proc/net/tcp which lists, for your networking namespace, the details of all open TCP/IP connections (look at /proc/net/tcp6 for TCP/IPv6 connections). This information includes the associated inode numbers. An example line from that file is

2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 19691 1 0000000000000000 100 0 0 10 0

The 00000000:0016 means that the socket is listening on 0.0.0.0 port 22. We know this is a listening socket since the peer address (00000000:0000) is all zeros. We can see that the inode for this socket is 19691.

Once armed with those numbers, you can match them up with the listings in /proc/<pid>/fd. For example, socket:[12345] means the socket is at inode 12345. Better yet, you can call stat on /proc/<pid>/fd/4 and pull the inode number from the struct stat object.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .