| This file documents how to use memory mapped I/O with netlink. | 
 |  | 
 | Author: Patrick McHardy <kaber@trash.net> | 
 |  | 
 | Overview | 
 | -------- | 
 |  | 
 | Memory mapped netlink I/O can be used to increase throughput and decrease | 
 | overhead of unicast receive and transmit operations. Some netlink subsystems | 
 | require high throughput, these are mainly the netfilter subsystems | 
 | nfnetlink_queue and nfnetlink_log, but it can also help speed up large | 
 | dump operations of f.i. the routing database. | 
 |  | 
 | Memory mapped netlink I/O used two circular ring buffers for RX and TX which | 
 | are mapped into the processes address space. | 
 |  | 
 | The RX ring is used by the kernel to directly construct netlink messages into | 
 | user-space memory without copying them as done with regular socket I/O, | 
 | additionally as long as the ring contains messages no recvmsg() or poll() | 
 | syscalls have to be issued by user-space to get more message. | 
 |  | 
 | The TX ring is used to process messages directly from user-space memory, the | 
 | kernel processes all messages contained in the ring using a single sendmsg() | 
 | call. | 
 |  | 
 | Usage overview | 
 | -------------- | 
 |  | 
 | In order to use memory mapped netlink I/O, user-space needs three main changes: | 
 |  | 
 | - ring setup | 
 | - conversion of the RX path to get messages from the ring instead of recvmsg() | 
 | - conversion of the TX path to construct messages into the ring | 
 |  | 
 | Ring setup is done using setsockopt() to provide the ring parameters to the | 
 | kernel, then a call to mmap() to map the ring into the processes address space: | 
 |  | 
 | - setsockopt(fd, SOL_NETLINK, NETLINK_RX_RING, ¶ms, sizeof(params)); | 
 | - setsockopt(fd, SOL_NETLINK, NETLINK_TX_RING, ¶ms, sizeof(params)); | 
 | - ring = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0) | 
 |  | 
 | Usage of either ring is optional, but even if only the RX ring is used the | 
 | mapping still needs to be writable in order to update the frame status after | 
 | processing. | 
 |  | 
 | Conversion of the reception path involves calling poll() on the file | 
 | descriptor, once the socket is readable the frames from the ring are | 
 | processed in order until no more messages are available, as indicated by | 
 | a status word in the frame header. | 
 |  | 
 | On kernel side, in order to make use of memory mapped I/O on receive, the | 
 | originating netlink subsystem needs to support memory mapped I/O, otherwise | 
 | it will use an allocated socket buffer as usual and the contents will be | 
 |  copied to the ring on transmission, nullifying most of the performance gains. | 
 | Dumps of kernel databases automatically support memory mapped I/O. | 
 |  | 
 | Conversion of the transmit path involves changing message construction to | 
 | use memory from the TX ring instead of (usually) a buffer declared on the | 
 | stack and setting up the frame header appropriately. Optionally poll() can | 
 | be used to wait for free frames in the TX ring. | 
 |  | 
 | Structured and definitions for using memory mapped I/O are contained in | 
 | <linux/netlink.h>. | 
 |  | 
 | RX and TX rings | 
 | ---------------- | 
 |  | 
 | Each ring contains a number of continuous memory blocks, containing frames of | 
 | fixed size dependent on the parameters used for ring setup. | 
 |  | 
 | Ring:	[ block 0 ] | 
 | 		[ frame 0 ] | 
 | 		[ frame 1 ] | 
 | 	[ block 1 ] | 
 | 		[ frame 2 ] | 
 | 		[ frame 3 ] | 
 | 	... | 
 | 	[ block n ] | 
 | 		[ frame 2 * n ] | 
 | 		[ frame 2 * n + 1 ] | 
 |  | 
 | The blocks are only visible to the kernel, from the point of view of user-space | 
 | the ring just contains the frames in a continuous memory zone. | 
 |  | 
 | The ring parameters used for setting up the ring are defined as follows: | 
 |  | 
 | struct nl_mmap_req { | 
 | 	unsigned int	nm_block_size; | 
 | 	unsigned int	nm_block_nr; | 
 | 	unsigned int	nm_frame_size; | 
 | 	unsigned int	nm_frame_nr; | 
 | }; | 
 |  | 
 | Frames are grouped into blocks, where each block is a continuous region of memory | 
 | and holds nm_block_size / nm_frame_size frames. The total number of frames in | 
 | the ring is nm_frame_nr. The following invariants hold: | 
 |  | 
 | - frames_per_block = nm_block_size / nm_frame_size | 
 |  | 
 | - nm_frame_nr = frames_per_block * nm_block_nr | 
 |  | 
 | Some parameters are constrained, specifically: | 
 |  | 
 | - nm_block_size must be a multiple of the architectures memory page size. | 
 |   The getpagesize() function can be used to get the page size. | 
 |  | 
 | - nm_frame_size must be equal or larger to NL_MMAP_HDRLEN, IOW a frame must be | 
 |   able to hold at least the frame header | 
 |  | 
 | - nm_frame_size must be smaller or equal to nm_block_size | 
 |  | 
 | - nm_frame_size must be a multiple of NL_MMAP_MSG_ALIGNMENT | 
 |  | 
 | - nm_frame_nr must equal the actual number of frames as specified above. | 
 |  | 
 | When the kernel can't allocate physically continuous memory for a ring block, | 
 | it will fall back to use physically discontinuous memory. This might affect | 
 | performance negatively, in order to avoid this the nm_frame_size parameter | 
 | should be chosen to be as small as possible for the required frame size and | 
 | the number of blocks should be increased instead. | 
 |  | 
 | Ring frames | 
 | ------------ | 
 |  | 
 | Each frames contain a frame header, consisting of a synchronization word and some | 
 | meta-data, and the message itself. | 
 |  | 
 | Frame:	[ header message ] | 
 |  | 
 | The frame header is defined as follows: | 
 |  | 
 | struct nl_mmap_hdr { | 
 | 	unsigned int	nm_status; | 
 | 	unsigned int	nm_len; | 
 | 	__u32		nm_group; | 
 | 	/* credentials */ | 
 | 	__u32		nm_pid; | 
 | 	__u32		nm_uid; | 
 | 	__u32		nm_gid; | 
 | }; | 
 |  | 
 | - nm_status is used for synchronizing processing between the kernel and user- | 
 |   space and specifies ownership of the frame as well as the operation to perform | 
 |  | 
 | - nm_len contains the length of the message contained in the data area | 
 |  | 
 | - nm_group specified the destination multicast group of message | 
 |  | 
 | - nm_pid, nm_uid and nm_gid contain the netlink pid, UID and GID of the sending | 
 |   process. These values correspond to the data available using SOCK_PASSCRED in | 
 |   the SCM_CREDENTIALS cmsg. | 
 |  | 
 | The possible values in the status word are: | 
 |  | 
 | - NL_MMAP_STATUS_UNUSED: | 
 | 	RX ring:	frame belongs to the kernel and contains no message | 
 | 			for user-space. Approriate action is to invoke poll() | 
 | 			to wait for new messages. | 
 |  | 
 | 	TX ring:	frame belongs to user-space and can be used for | 
 | 			message construction. | 
 |  | 
 | - NL_MMAP_STATUS_RESERVED: | 
 | 	RX ring only:	frame is currently used by the kernel for message | 
 | 			construction and contains no valid message yet. | 
 | 			Appropriate action is to invoke poll() to wait for | 
 | 			new messages. | 
 |  | 
 | - NL_MMAP_STATUS_VALID: | 
 | 	RX ring:	frame contains a valid message. Approriate action is | 
 | 			to process the message and release the frame back to | 
 | 			the kernel by setting the status to | 
 | 			NL_MMAP_STATUS_UNUSED or queue the frame by setting the | 
 | 			status to NL_MMAP_STATUS_SKIP. | 
 |  | 
 | 	TX ring:	the frame contains a valid message from user-space to | 
 | 			be processed by the kernel. After completing processing | 
 | 			the kernel will release the frame back to user-space by | 
 | 			setting the status to NL_MMAP_STATUS_UNUSED. | 
 |  | 
 | - NL_MMAP_STATUS_COPY: | 
 | 	RX ring only:	a message is ready to be processed but could not be | 
 | 			stored in the ring, either because it exceeded the | 
 | 			frame size or because the originating subsystem does | 
 | 			not support memory mapped I/O. Appropriate action is | 
 | 			to invoke recvmsg() to receive the message and release | 
 | 			the frame back to the kernel by setting the status to | 
 | 			NL_MMAP_STATUS_UNUSED. | 
 |  | 
 | - NL_MMAP_STATUS_SKIP: | 
 | 	RX ring only:	user-space queued the message for later processing, but | 
 | 			processed some messages following it in the ring. The | 
 | 			kernel should skip this frame when looking for unused | 
 | 			frames. | 
 |  | 
 | The data area of a frame begins at a offset of NL_MMAP_HDRLEN relative to the | 
 | frame header. | 
 |  | 
 | TX limitations | 
 | -------------- | 
 |  | 
 | Kernel processing usually involves validation of the message received by | 
 | user-space, then processing its contents. The kernel must assure that | 
 | userspace is not able to modify the message contents after they have been | 
 | validated. In order to do so, the message is copied from the ring frame | 
 | to an allocated buffer if either of these conditions is false: | 
 |  | 
 | - only a single mapping of the ring exists | 
 | - the file descriptor is not shared between processes | 
 |  | 
 | This means that for threaded programs, the kernel will fall back to copying. | 
 |  | 
 | Example | 
 | ------- | 
 |  | 
 | Ring setup: | 
 |  | 
 | 	unsigned int block_size = 16 * getpagesize(); | 
 | 	struct nl_mmap_req req = { | 
 | 		.nm_block_size		= block_size, | 
 | 		.nm_block_nr		= 64, | 
 | 		.nm_frame_size		= 16384, | 
 | 		.nm_frame_nr		= 64 * block_size / 16384, | 
 | 	}; | 
 | 	unsigned int ring_size; | 
 | 	void *rx_ring, *tx_ring; | 
 |  | 
 | 	/* Configure ring parameters */ | 
 | 	if (setsockopt(fd, NETLINK_RX_RING, &req, sizeof(req)) < 0) | 
 | 		exit(1); | 
 | 	if (setsockopt(fd, NETLINK_TX_RING, &req, sizeof(req)) < 0) | 
 | 		exit(1) | 
 |  | 
 | 	/* Calculate size of each individual ring */ | 
 | 	ring_size = req.nm_block_nr * req.nm_block_size; | 
 |  | 
 | 	/* Map RX/TX rings. The TX ring is located after the RX ring */ | 
 | 	rx_ring = mmap(NULL, 2 * ring_size, PROT_READ | PROT_WRITE, | 
 | 		       MAP_SHARED, fd, 0); | 
 | 	if ((long)rx_ring == -1L) | 
 | 		exit(1); | 
 | 	tx_ring = rx_ring + ring_size: | 
 |  | 
 | Message reception: | 
 |  | 
 | This example assumes some ring parameters of the ring setup are available. | 
 |  | 
 | 	unsigned int frame_offset = 0; | 
 | 	struct nl_mmap_hdr *hdr; | 
 | 	struct nlmsghdr *nlh; | 
 | 	unsigned char buf[16384]; | 
 | 	ssize_t len; | 
 |  | 
 | 	while (1) { | 
 | 		struct pollfd pfds[1]; | 
 |  | 
 | 		pfds[0].fd	= fd; | 
 | 		pfds[0].events	= POLLIN | POLLERR; | 
 | 		pfds[0].revents	= 0; | 
 |  | 
 | 		if (poll(pfds, 1, -1) < 0 && errno != -EINTR) | 
 | 			exit(1); | 
 |  | 
 | 		/* Check for errors. Error handling omitted */ | 
 | 		if (pfds[0].revents & POLLERR) | 
 | 			<handle error> | 
 |  | 
 | 		/* If no new messages, poll again */ | 
 | 		if (!(pfds[0].revents & POLLIN)) | 
 | 			continue; | 
 |  | 
 | 		/* Process all frames */ | 
 | 		while (1) { | 
 | 			/* Get next frame header */ | 
 | 			hdr = rx_ring + frame_offset; | 
 |  | 
 | 			if (hdr->nm_status == NL_MMAP_STATUS_VALID) { | 
 | 				/* Regular memory mapped frame */ | 
 | 				nlh = (void *)hdr + NL_MMAP_HDRLEN; | 
 | 				len = hdr->nm_len; | 
 |  | 
 | 				/* Release empty message immediately. May happen | 
 | 				 * on error during message construction. | 
 | 				 */ | 
 | 				if (len == 0) | 
 | 					goto release; | 
 | 			} else if (hdr->nm_status == NL_MMAP_STATUS_COPY) { | 
 | 				/* Frame queued to socket receive queue */ | 
 | 				len = recv(fd, buf, sizeof(buf), MSG_DONTWAIT); | 
 | 				if (len <= 0) | 
 | 					break; | 
 | 				nlh = buf; | 
 | 			} else | 
 | 				/* No more messages to process, continue polling */ | 
 | 				break; | 
 |  | 
 | 			process_msg(nlh); | 
 | release: | 
 | 			/* Release frame back to the kernel */ | 
 | 			hdr->nm_status = NL_MMAP_STATUS_UNUSED; | 
 |  | 
 | 			/* Advance frame offset to next frame */ | 
 | 			frame_offset = (frame_offset + frame_size) % ring_size; | 
 | 		} | 
 | 	} | 
 |  | 
 | Message transmission: | 
 |  | 
 | This example assumes some ring parameters of the ring setup are available. | 
 | A single message is constructed and transmitted, to send multiple messages | 
 | at once they would be constructed in consecutive frames before a final call | 
 | to sendto(). | 
 |  | 
 | 	unsigned int frame_offset = 0; | 
 | 	struct nl_mmap_hdr *hdr; | 
 | 	struct nlmsghdr *nlh; | 
 | 	struct sockaddr_nl addr = { | 
 | 		.nl_family	= AF_NETLINK, | 
 | 	}; | 
 |  | 
 | 	hdr = tx_ring + frame_offset; | 
 | 	if (hdr->nm_status != NL_MMAP_STATUS_UNUSED) | 
 | 		/* No frame available. Use poll() to avoid. */ | 
 | 		exit(1); | 
 |  | 
 | 	nlh = (void *)hdr + NL_MMAP_HDRLEN; | 
 |  | 
 | 	/* Build message */ | 
 | 	build_message(nlh); | 
 |  | 
 | 	/* Fill frame header: length and status need to be set */ | 
 | 	hdr->nm_len	= nlh->nlmsg_len; | 
 | 	hdr->nm_status	= NL_MMAP_STATUS_VALID; | 
 |  | 
 | 	if (sendto(fd, NULL, 0, 0, &addr, sizeof(addr)) < 0) | 
 | 		exit(1); | 
 |  | 
 | 	/* Advance frame offset to next frame */ | 
 | 	frame_offset = (frame_offset + frame_size) % ring_size; |