This article was written by Scott James Remnant on 2011-02-09, and published here, where it is no longer available. I’ve liberated it from the Internet Archive–a fine organization which you ought support–because it is useful.
The proc connector is one of those interesting kernel features that most people rarely come across, and even more rarely find documentation on. Likewise the socket filter. This is a shame, because they’re both really quite useful interfaces that might serve a variety of purposes if they were better documented.
The proc connector allows you to receive notification of process events such fork and exec calls, as well as changes to a process’s uid, gid or sid (session id). These are provided through a socket-based interface by reading instances of struct proc_event defined in the kernel header.
#include
The interface is built on the more generic connector API, which itself is built on the generic netlink API. These interfaces add some complexity as they are intended to provide bi-directional communication between the kernel and userspace; the connector API appears to have been largely forgotten as newer such socket interfaces simply declare their own first-class socket classes. So we need the headers for those too.
#include#include
(For brevity, I’ll omit any standard boilerplate such as the headers you need for syscalls and library functions that you should be used to as well as function definitions, error checking, and so-forth.)
Ok, now we’re ready to create the connector socket. This is straight-forward enough, since we’re dealing with atomic messages rather than a stream, datagram is appropriate.
int sock; sock = socket (PF_NETLINK, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, NETLINK_CONNECTOR);
To select the proc connector we bind the socket using a struct sockaddr_nl object.
struct sockaddr_nl addr; addr.nl_family = AF_NETLINK; addr.nl_pid = getpid (); addr.nl_groups = CN_IDX_PROC; bind (sock, (struct sockaddr *)&addr, sizeof(addr));
Unfortunately that’s not quite enough yet; the proc connector socket is a bit of a firehose, so it doesn’t in fact send any messages until a process has subscribed to it. So we have to send a subscription message.
As I mentioned before, the proc connector is built on top of the generic connector and that itself is on top of netlink so sending that subscription message also involves embedded a message, inside a message inside a message. If you understood Christopher Nolan’s Inception, you should do just fine.
Since we’re nesting a proc connector operation message inside a connector message inside a netlink message, it’s easiest to use an iovec for this kind of thing.
struct iovec iov[3]; char nlmsghdrbuf[NLMSG_LENGTH (0)]; struct nlmsghdr *nlmsghdr = nlmsghdrbuf; struct cn_msg cn_msg; enum proc_cn_mcast_op op; nlmsghdr->nlmsg_len = NLMSG_LENGTH (sizeof(cn_msg) + sizeof(op)); nlmsghdr->nlmsg_type = NLMSG_DONE; nlmsghdr->nlmsg_flags = 0; nlmsghdr->nlmsg_seq = 0; nlmsghdr->nlmsg_pid = getpid (); iov[0].iov_base = nlmsghdrbuf; iov[0].iov_len = NLMSG_LENGTH (0); cn_msg.id.idx = CN_IDX_PROC; cn_msg.id.val = CN_VAL_PROC; cn_msg.seq = 0; cn_msg.ack = 0; cn_msg.len = sizeof op; iov[1].iov_base = &cn_msg; iov[1].iov_len = sizeof cn_msg; op = PROC_CN_MCAST_LISTEN; iov[2].iov_base = &op; iov[2].iov_len = sizeof(op); writev (sock, iov, 3);
The netlink message length is the combined length of the following connector and proc connector operation messages, and is otherwise simply a message from our process id with no following messages. However all of the interfaces to netlink take a lot of care to make sure the following structure in the message is aligned as wide as possible using the NLMSG_LENGTH macro, to avoid issues with platforms that have fixed alignment for data types, so we have to be careful of that too.
So we actually have a bit of padding between the struct nlmsghdr and the struct cn_msg, this is accomplished by actually using a character buffer of the right size for the first iovec element and accessing it through a struct nlmsghdr pointer.
The connector message indicates that it is relevant to the proc connector through the idx and val fields, and the length is the legnth of the proc connector operation message.
Finally the proc connector operation message (just an enum) says we want to subscribe. Why isn’t there padding between the connector and proc connector operation messages? Because the last element in struct cn_msg is a zero-width type which results in