···11+This file documents how to use memory mapped I/O with netlink.22+33+Author: Patrick McHardy <kaber@trash.net>44+55+Overview66+--------77+88+Memory mapped netlink I/O can be used to increase throughput and decrease99+overhead of unicast receive and transmit operations. Some netlink subsystems1010+require high throughput, these are mainly the netfilter subsystems1111+nfnetlink_queue and nfnetlink_log, but it can also help speed up large1212+dump operations of f.i. the routing database.1313+1414+Memory mapped netlink I/O used two circular ring buffers for RX and TX which1515+are mapped into the processes address space.1616+1717+The RX ring is used by the kernel to directly construct netlink messages into1818+user-space memory without copying them as done with regular socket I/O,1919+additionally as long as the ring contains messages no recvmsg() or poll()2020+syscalls have to be issued by user-space to get more message.2121+2222+The TX ring is used to process messages directly from user-space memory, the2323+kernel processes all messages contained in the ring using a single sendmsg()2424+call.2525+2626+Usage overview2727+--------------2828+2929+In order to use memory mapped netlink I/O, user-space needs three main changes:3030+3131+- ring setup3232+- conversion of the RX path to get messages from the ring instead of recvmsg()3333+- conversion of the TX path to construct messages into the ring3434+3535+Ring setup is done using setsockopt() to provide the ring parameters to the3636+kernel, then a call to mmap() to map the ring into the processes address space:3737+3838+- setsockopt(fd, SOL_NETLINK, NETLINK_RX_RING, ¶ms, sizeof(params));3939+- setsockopt(fd, SOL_NETLINK, NETLINK_TX_RING, ¶ms, sizeof(params));4040+- ring = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)4141+4242+Usage of either ring is optional, but even if only the RX ring is used the4343+mapping still needs to be writable in order to update the frame status after4444+processing.4545+4646+Conversion of the reception path involves calling poll() on the file4747+descriptor, once the socket is readable the frames from the ring are4848+processsed in order until no more messages are available, as indicated by4949+a status word in the frame header.5050+5151+On kernel side, in order to make use of memory mapped I/O on receive, the5252+originating netlink subsystem needs to support memory mapped I/O, otherwise5353+it will use an allocated socket buffer as usual and the contents will be5454+ copied to the ring on transmission, nullifying most of the performance gains.5555+Dumps of kernel databases automatically support memory mapped I/O.5656+5757+Conversion of the transmit path involves changing message contruction to5858+use memory from the TX ring instead of (usually) a buffer declared on the5959+stack and setting up the frame header approriately. Optionally poll() can6060+be used to wait for free frames in the TX ring.6161+6262+Structured and definitions for using memory mapped I/O are contained in6363+<linux/netlink.h>.6464+6565+RX and TX rings6666+----------------6767+6868+Each ring contains a number of continous memory blocks, containing frames of6969+fixed size dependant on the parameters used for ring setup.7070+7171+Ring: [ block 0 ]7272+ [ frame 0 ]7373+ [ frame 1 ]7474+ [ block 1 ]7575+ [ frame 2 ]7676+ [ frame 3 ]7777+ ...7878+ [ block n ]7979+ [ frame 2 * n ]8080+ [ frame 2 * n + 1 ]8181+8282+The blocks are only visible to the kernel, from the point of view of user-space8383+the ring just contains the frames in a continous memory zone.8484+8585+The ring parameters used for setting up the ring are defined as follows:8686+8787+struct nl_mmap_req {8888+ unsigned int nm_block_size;8989+ unsigned int nm_block_nr;9090+ unsigned int nm_frame_size;9191+ unsigned int nm_frame_nr;9292+};9393+9494+Frames are grouped into blocks, where each block is a continous region of memory9595+and holds nm_block_size / nm_frame_size frames. The total number of frames in9696+the ring is nm_frame_nr. The following invariants hold:9797+9898+- frames_per_block = nm_block_size / nm_frame_size9999+100100+- nm_frame_nr = frames_per_block * nm_block_nr101101+102102+Some parameters are constrained, specifically:103103+104104+- nm_block_size must be a multiple of the architectures memory page size.105105+ The getpagesize() function can be used to get the page size.106106+107107+- nm_frame_size must be equal or larger to NL_MMAP_HDRLEN, IOW a frame must be108108+ able to hold at least the frame header109109+110110+- nm_frame_size must be smaller or equal to nm_block_size111111+112112+- nm_frame_size must be a multiple of NL_MMAP_MSG_ALIGNMENT113113+114114+- nm_frame_nr must equal the actual number of frames as specified above.115115+116116+When the kernel can't allocate phsyically continous memory for a ring block,117117+it will fall back to use physically discontinous memory. This might affect118118+performance negatively, in order to avoid this the nm_frame_size parameter119119+should be chosen to be as small as possible for the required frame size and120120+the number of blocks should be increased instead.121121+122122+Ring frames123123+------------124124+125125+Each frames contain a frame header, consisting of a synchronization word and some126126+meta-data, and the message itself.127127+128128+Frame: [ header message ]129129+130130+The frame header is defined as follows:131131+132132+struct nl_mmap_hdr {133133+ unsigned int nm_status;134134+ unsigned int nm_len;135135+ __u32 nm_group;136136+ /* credentials */137137+ __u32 nm_pid;138138+ __u32 nm_uid;139139+ __u32 nm_gid;140140+};141141+142142+- nm_status is used for synchronizing processing between the kernel and user-143143+ space and specifies ownership of the frame as well as the operation to perform144144+145145+- nm_len contains the length of the message contained in the data area146146+147147+- nm_group specified the destination multicast group of message148148+149149+- nm_pid, nm_uid and nm_gid contain the netlink pid, UID and GID of the sending150150+ process. These values correspond to the data available using SOCK_PASSCRED in151151+ the SCM_CREDENTIALS cmsg.152152+153153+The possible values in the status word are:154154+155155+- NL_MMAP_STATUS_UNUSED:156156+ RX ring: frame belongs to the kernel and contains no message157157+ for user-space. Approriate action is to invoke poll()158158+ to wait for new messages.159159+160160+ TX ring: frame belongs to user-space and can be used for161161+ message construction.162162+163163+- NL_MMAP_STATUS_RESERVED:164164+ RX ring only: frame is currently used by the kernel for message165165+ construction and contains no valid message yet.166166+ Appropriate action is to invoke poll() to wait for167167+ new messages.168168+169169+- NL_MMAP_STATUS_VALID:170170+ RX ring: frame contains a valid message. Approriate action is171171+ to process the message and release the frame back to172172+ the kernel by setting the status to173173+ NL_MMAP_STATUS_UNUSED or queue the frame by setting the174174+ status to NL_MMAP_STATUS_SKIP.175175+176176+ TX ring: the frame contains a valid message from user-space to177177+ be processed by the kernel. After completing processing178178+ the kernel will release the frame back to user-space by179179+ setting the status to NL_MMAP_STATUS_UNUSED.180180+181181+- NL_MMAP_STATUS_COPY:182182+ RX ring only: a message is ready to be processed but could not be183183+ stored in the ring, either because it exceeded the184184+ frame size or because the originating subsystem does185185+ not support memory mapped I/O. Appropriate action is186186+ to invoke recvmsg() to receive the message and release187187+ the frame back to the kernel by setting the status to188188+ NL_MMAP_STATUS_UNUSED.189189+190190+- NL_MMAP_STATUS_SKIP:191191+ RX ring only: user-space queued the message for later processing, but192192+ processed some messages following it in the ring. The193193+ kernel should skip this frame when looking for unused194194+ frames.195195+196196+The data area of a frame begins at a offset of NL_MMAP_HDRLEN relative to the197197+frame header.198198+199199+TX limitations200200+--------------201201+202202+Kernel processing usually involves validation of the message received by203203+user-space, then processing its contents. The kernel must assure that204204+userspace is not able to modify the message contents after they have been205205+validated. In order to do so, the message is copied from the ring frame206206+to an allocated buffer if either of these conditions is false:207207+208208+- only a single mapping of the ring exists209209+- the file descriptor is not shared between processes210210+211211+This means that for threaded programs, the kernel will fall back to copying.212212+213213+Example214214+-------215215+216216+Ring setup:217217+218218+ unsigned int block_size = 16 * getpagesize();219219+ struct nl_mmap_req req = {220220+ .nm_block_size = block_size,221221+ .nm_block_nr = 64,222222+ .nm_frame_size = 16384,223223+ .nm_frame_nr = 64 * block_size / 16384,224224+ };225225+ unsigned int ring_size;226226+ void *rx_ring, *tx_ring;227227+228228+ /* Configure ring parameters */229229+ if (setsockopt(fd, NETLINK_RX_RING, &req, sizeof(req)) < 0)230230+ exit(1);231231+ if (setsockopt(fd, NETLINK_TX_RING, &req, sizeof(req)) < 0)232232+ exit(1)233233+234234+ /* Calculate size of each invididual ring */235235+ ring_size = req.nm_block_nr * req.nm_block_size;236236+237237+ /* Map RX/TX rings. The TX ring is located after the RX ring */238238+ rx_ring = mmap(NULL, 2 * ring_size, PROT_READ | PROT_WRITE,239239+ MAP_SHARED, fd, 0);240240+ if ((long)rx_ring == -1L)241241+ exit(1);242242+ tx_ring = rx_ring + ring_size:243243+244244+Message reception:245245+246246+This example assumes some ring parameters of the ring setup are available.247247+248248+ unsigned int frame_offset = 0;249249+ struct nl_mmap_hdr *hdr;250250+ struct nlmsghdr *nlh;251251+ unsigned char buf[16384];252252+ ssize_t len;253253+254254+ while (1) {255255+ struct pollfd pfds[1];256256+257257+ pfds[0].fd = fd;258258+ pfds[0].events = POLLIN | POLLERR;259259+ pfds[0].revents = 0;260260+261261+ if (poll(pfds, 1, -1) < 0 && errno != -EINTR)262262+ exit(1);263263+264264+ /* Check for errors. Error handling omitted */265265+ if (pfds[0].revents & POLLERR)266266+ <handle error>267267+268268+ /* If no new messages, poll again */269269+ if (!(pfds[0].revents & POLLIN))270270+ continue;271271+272272+ /* Process all frames */273273+ while (1) {274274+ /* Get next frame header */275275+ hdr = rx_ring + frame_offset;276276+277277+ if (hdr->nm_status == NL_MMAP_STATUS_VALID)278278+ /* Regular memory mapped frame */279279+ nlh = (void *hdr) + NL_MMAP_HDRLEN;280280+ len = hdr->nm_len;281281+282282+ /* Release empty message immediately. May happen283283+ * on error during message construction.284284+ */285285+ if (len == 0)286286+ goto release;287287+ } else if (hdr->nm_status == NL_MMAP_STATUS_COPY) {288288+ /* Frame queued to socket receive queue */289289+ len = recv(fd, buf, sizeof(buf), MSG_DONTWAIT);290290+ if (len <= 0)291291+ break;292292+ nlh = buf;293293+ } else294294+ /* No more messages to process, continue polling */295295+ break;296296+297297+ process_msg(nlh);298298+release:299299+ /* Release frame back to the kernel */300300+ hdr->nm_status = NL_MMAP_STATUS_UNUSED;301301+302302+ /* Advance frame offset to next frame */303303+ frame_offset = (frame_offset + frame_size) % ring_size;304304+ }305305+ }306306+307307+Message transmission:308308+309309+This example assumes some ring parameters of the ring setup are available.310310+A single message is constructed and transmitted, to send multiple messages311311+at once they would be constructed in consecutive frames before a final call312312+to sendto().313313+314314+ unsigned int frame_offset = 0;315315+ struct nl_mmap_hdr *hdr;316316+ struct nlmsghdr *nlh;317317+ struct sockaddr_nl addr = {318318+ .nl_family = AF_NETLINK,319319+ };320320+321321+ hdr = tx_ring + frame_offset;322322+ if (hdr->nm_status != NL_MMAP_STATUS_UNUSED)323323+ /* No frame available. Use poll() to avoid. */324324+ exit(1);325325+326326+ nlh = (void *)hdr + NL_MMAP_HDRLEN;327327+328328+ /* Build message */329329+ build_message(nlh);330330+331331+ /* Fill frame header: length and status need to be set */332332+ hdr->nm_len = nlh->nlmsg_len;333333+ hdr->nm_status = NL_MMAP_STATUS_VALID;334334+335335+ if (sendto(fd, NULL, 0, 0, &addr, sizeof(addr)) < 0)336336+ exit(1);337337+338338+ /* Advance frame offset to next frame */339339+ frame_offset = (frame_offset + frame_size) % ring_size;