diff --git a/22-netlink/cn_proc.c b/22-netlink/cn_proc.c
index 75ed455..363a4d6 100644
--- a/22-netlink/cn_proc.c
+++ b/22-netlink/cn_proc.c
@@ -33,7 +33,25 @@ static char *execname(pid_t pid, char *buf, size_t bufsize) {
// and "connect" it. Please note that these sockets are not
// connect(2)'ed, but we bind it to the correct sockaddr_nl address.
int cn_proc_connect() {
- return -1;
+ // Create a netlink socket. Datagram-oriented is just fine here.
+ int sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
+ if (sock < 0) die("socket");
+
+ // We bind the socket to the given CN (connector netlink) address.
+ struct sockaddr_nl addr;
+ addr.nl_family = AF_NETLINK;
+ addr.nl_groups = CN_IDX_PROC; // The cn_proc multicast group
+
+ // nl_pid is special: It is the unique identifier of our netlink
+ // socket (like a UDP port). By setting it to 0, we let the
+ // kernel assign an unique address. See netlink(7), nl_link
+ addr.nl_pid = 0;
+
+ // Bind the socket! Addr: AF_NETLINK, mcast-group CN_IDX_PROC, our unique id: by kernel
+ if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0)
+ die("bind");
+
+ return sock;
}
// Without configuration, the Linux kernel does not create
@@ -59,16 +77,76 @@ void cn_proc_configure(int sock_fd, bool enable) {
// transport.
// The padding is: NLMSG_LENGTH(0) - sizeof(struct nlmsghdr)
- // FIXME: Initialize struct nlmsghdr, struct cn_msg, and struct proc_cn_mcast_op
- // FIXME: Create an iovec with 4 elements (don't forget the padding)
- // FIXME: You can send the message with writev(2)
+ // The mcast operation.
+ enum proc_cn_mcast_op mcast =
+ enable ? PROC_CN_MCAST_LISTEN : PROC_CN_MCAST_IGNORE;
+
+ // The connector message header indicates that this message is
+ // directed at the cn_proc component
+ struct cn_msg cn_hdr = {
+ .id = {.idx = CN_IDX_PROC, .val = CN_VAL_PROC },
+ .seq = 0, .ack = 0,
+ .len = sizeof(mcast), // Length of the following payload
+ };
+
+ // The netlink header
+ struct nlmsghdr nl_hdr = {
+ // Length of the following payload
+ .nlmsg_len = NLMSG_LENGTH(sizeof(cn_hdr) + sizeof(mcast)),
+ // Last (and first) part of this message
+ .nlmsg_type = NLMSG_DONE,
+ };
+
+ // On some architectures, a few padding bytes are required between
+ // the netlink header and the netlink payload. On amd64, you get
+ // away with forgetting this.
+ char padding[NLMSG_LENGTH(0) - sizeof(struct nlmsghdr)];
+
+ // A scattered iovec that assembles the netlink message
+ struct iovec vec[] = {
+ { .iov_base = &nl_hdr, .iov_len=sizeof(nl_hdr) }, // 1.
+ { .iov_base = &padding, .iov_len=sizeof(padding) }, // 2.
+ { .iov_base = &cn_hdr, .iov_len=sizeof(cn_hdr) }, // 3.
+ { .iov_base = &mcast, .iov_len=sizeof(mcast) } // 4.
+ };
+
+ // Send it to the kernel
+ if (writev(sock_fd, vec, sizeof(vec)/sizeof(*vec)) != nl_hdr.nlmsg_len)
+ die("sendmsg");
+
}
// This function handles a single cn_proc event and prints it to the terminal
void cn_proc_handle(struct proc_event *ev) {
// See /usr/include/linux/cn_proc.h for details
- printf("ev->what: %d\n", ev->what);
+ char buf[256]; // Buffer for execname
+
+ switch(ev->what){
+ case PROC_EVENT_FORK:
+ printf("fork(): %20s (%d, %d) -> (%d, %d)\n",
+ execname(ev->event_data.fork.parent_tgid, buf, sizeof(buf)),
+ ev->event_data.fork.parent_tgid,
+ ev->event_data.fork.parent_pid,
+ ev->event_data.fork.child_tgid,
+ ev->event_data.fork.child_pid);
+ break;
+ case PROC_EVENT_EXEC:
+ printf("exec(): %20s (%d, %d)\n",
+ execname(ev->event_data.exec.process_tgid, buf, sizeof(buf)),
+ ev->event_data.exec.process_tgid,
+ ev->event_data.exec.process_pid);
+ break;
+ case PROC_EVENT_EXIT:
+ printf("exit(): %20s (%d, %d) -> rc=%d\n",
+ "",
+ ev->event_data.exit.process_tgid,
+ ev->event_data.exit.process_pid,
+ ev->event_data.exit.exit_code);
+ break;
+ default:
+ break;
+ }
}
@@ -87,8 +165,39 @@ int main(int argc, char **argv) {
return -1;
}
- // FIXME: Create cn_proc socket and enable mcast
- // FIXME: recv(2) data from the socket and iterate over nl message (see netlink(7))
- // FIXME: For NLMSG_DONE messages, invoke cn_proc_handle on the (struct proc_event*)cn_hdr->data
+ // Create a cn_proc socket and enable the mcast group
+ cn_proc_fd = cn_proc_connect();
+ atexit(cn_proc_atexit);
+ cn_proc_configure(cn_proc_fd, true);
+
+ // Receive events from the kernel
+ while (true) {
+ // Get multiple netlink messages from the kernel
+ char buf[4096];
+ int len = recv(cn_proc_fd, buf, sizeof(buf), 0);
+ if (len < 0) die("recv");
+
+ // Iterate over the netlink messages in the buffer. For this,
+ // we use the helper macros from netlink(3) as netlink message
+ // are weirdly (with padding) within the receive buffer
+ for (/* init */ struct nlmsghdr *nlh = (struct nlmsghdr *) buf;
+ /* cond */ NLMSG_OK (nlh, len);
+ /* next */ nlh = NLMSG_NEXT (nlh, len)) {
+
+ // Only complete messages (ignore NOOP, ERROR, OVERRUN, whatever)
+ if (nlh->nlmsg_type != NLMSG_DONE)
+ continue;
+
+ // All messages should come from cn_proc, but you never
+ // can be sure.
+ struct cn_msg *cn_msg = NLMSG_DATA(nlh);
+ if ((cn_msg->id.idx != CN_IDX_PROC)
+ || (cn_msg->id.val != CN_VAL_PROC))
+ continue;
+
+ // cn_msg->data contains a single proc_event
+ cn_proc_handle((struct proc_event*)cn_msg->data);
+ }
+ }
return 0;
}