요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. SPDX-License-Identifier: GPL-2.0
======
AF_XDP
======
Overview
========
AF_XDP is an address family that is optimized for high performance
packet processing.
This document assumes that the reader is familiar with BPF and XDP. If
not, the Cilium project has an excellent reference guide at
http://cilium.readthedocs.io/en/latest/bpf/.
Using the XDP_REDIRECT action from an XDP program, the program can
redirect ingress frames to other XDP enabled netdevs, using the
bpf_redirect_map() function. AF_XDP sockets enable the possibility for
XDP programs to redirect frames to a memory buffer in a user-space
application.
An AF_XDP socket (XSK) is created with the normal socket()
syscall. Associated with each XSK are two rings: the RX ring and the
TX ring. A socket can receive packets on the RX ring and it can send
packets on the TX ring. These rings are registered and sized with the
setsockopts XDP_RX_RING and XDP_TX_RING, respectively. It is mandatory
to have at least one of these rings for each socket. An RX or TX
descriptor ring points to a data buffer in a memory area called a
UMEM. RX and TX can share the same UMEM so that a packet does not have
to be copied between RX and TX. Moreover, if a packet needs to be kept
for a while due to a possible retransmit, the descriptor that points
to that packet can be changed to point to another and reused right
away. This again avoids copying data.
The UMEM consists of a number of equally sized chunks. A descriptor in
one of the rings references a frame by referencing its addr. The addr
is simply an offset within the entire UMEM region. The user space
allocates memory for this UMEM using whatever means it feels is most
appropriate (malloc, mmap, huge pages, etc). This memory area is then
registered with the kernel using the new setsockopt XDP_UMEM_REG. The
UMEM also has two rings: the FILL ring and the COMPLETION ring. The
FILL ring is used by the application to send down addr for the kernel
to fill in with RX packet data. References to these frames will then
appear in the RX ring once each packet has been received. The
COMPLETION ring, on the other hand, contains frame addr that the
kernel has transmitted completely and can now be used again by user
space, for either TX or RX. Thus, the frame addrs appearing in the
COMPLETION ring are addrs that were previously transmitted using the
TX ring. In summary, the RX and FILL rings are used for the RX path
and the TX and COMPLETION rings are used for the TX path.
The socket is then finally bound with a bind() call to a device and a
specific queue id on that device, and it is not until bind is
completed that traffic starts to flow.
The UMEM can be shared between processes, if desired. If a process
wants to do this, it simply skips the registration of the UMEM and its
corresponding two rings, sets the XDP_SHARED_UMEM flag in the bind
call and submits the XSK of the process it would like to share UMEM
with as well as its own newly created XSK socket. The new process will
then receive frame addr references in its own RX ring that point to
this shared UMEM. Note that since the ring structures are
single-consumer / single-producer (for performance reasons), the new
process has to create its own socket with associated RX and TX rings,
since it cannot share this with the other process. This is also the
reason that there is only one set of FILL and COMPLETION rings per
UMEM. It is the responsibility of a single process to handle the UMEM.
How is then packets distributed from an XDP program to the XSKs? There
is a BPF map called XSKMAP (or BPF_MAP_TYPE_XSKMAP in full). The
user-space application can place an XSK at an arbitrary place in this
map. The XDP program can then redirect a packet to a specific index in
this map and at this point XDP validates that the XSK in that map was
indeed bound to that device and ring number. If not, the packet is
dropped. If the map is empty at that index, the packet is also
dropped. This also means that it is currently mandatory to have an XDP
program loaded (and one XSK in the XSKMAP) to be able to get any
traffic to user space through the XSK.
AF_XDP can operate in two different modes: XDP_SKB and XDP_DRV. If the
driver does not have support for XDP, or XDP_SKB is explicitly chosen
when loading the XDP program, XDP_SKB mode is employed that uses SKBs
together with the generic XDP support and copies out the data to user
space. A fallback mode that works for any network device. On the other
hand, if the driver has support for XDP, it will be used by the AF_XDP
code to provide better performance, but there is still a copy of the
data into user space.
Concepts
========
In order to use an AF_XDP socket, a number of associated objects need
to be setup. These objects and their options are explained in the
following sections.
For an overview on how AF_XDP works, you can also take a look at the
Linux Plumbers paper from 2018 on the subject:
http://vger.kernel.org/lpc_net2018_talks/lpc18_paper_af_xdp_perf-v2.pdf. Do
NOT consult the paper from 2017 on "AF_PACKET v4", the first attempt
at AF_XDP. Nearly everything changed since then. Jonathan Corbet has
also written an excellent article on LWN, "Accelerating networking
with AF_XDP". It can be found at https://lwn.net/Articles/750845/.
UMEM
----
UMEM is a region of virtual contiguous memory, divided into
equal-sized frames. An UMEM is associated to a netdev and a specific
queue id of that netdev. It is created and configured (chunk size,
headroom, start address and size) by using the XDP_UMEM_REG setsockopt
system call. A UMEM is bound to a netdev and queue id, via the bind()
system call.
An AF_XDP is socket linked to a single UMEM, but one UMEM can have
multiple AF_XDP sockets. To share an UMEM created via one socket A,
the next socket B can do this by setting the XDP_SHARED_UMEM flag in
struct sockaddr_xdp member sxdp_flags, and passing the file descriptor
of A to struct sockaddr_xdp member sxdp_shared_umem_fd.
The UMEM has two single-producer/single-consumer rings that are used
to transfer ownership of UMEM frames between the kernel and the
user-space application.
Rings
-----
There are a four different kind of rings: FILL, COMPLETION, RX and
TX. All rings are single-producer/single-consumer, so the user-space
application need explicit synchronization of multiple
processes/threads are reading/writing to them.
The UMEM uses two rings: FILL and COMPLETION. Each socket associated
with the UMEM must have an RX queue, TX queue or both. Say, that there
is a setup with four sockets (all doing TX and RX). Then there will be
one FILL ring, one COMPLETION ring, four TX rings and four RX rings.
The rings are head(producer)/tail(consumer) based rings. A producer
writes the data ring at the index pointed out by struct xdp_ring
producer member, and increasing the producer index. A consumer reads
the data ring at the index pointed out by struct xdp_ring consumer
member, and increasing the consumer index.
The rings are configured and created via the _RING setsockopt system
calls and mmapped to user-space using the appropriate offset to mmap()
(XDP_PGOFF_RX_RING, XDP_PGOFF_TX_RING, XDP_UMEM_PGOFF_FILL_RING and
XDP_UMEM_PGOFF_COMPLETION_RING).
The size of the rings need to be of size power of two.
UMEM Fill Ring
~~~~~~~~~~~~~~
The FILL ring is used to transfer ownership of UMEM frames from
user-space to kernel-space. The UMEM addrs are passed in the ring. As
an example, if the UMEM is 64k and each chunk is 4k, then the UMEM has
16 chunks and can pass addrs between 0 and 64k.
Frames passed to the kernel are used for the ingress path (RX rings).
The user application produces UMEM addrs to this ring. Note that, if
running the application with aligned chunk mode, the kernel will mask
the incoming addr. E.g. for a chunk size of 2k, the log2(2048) LSB of
the addr will be masked off, meaning that 2048, 2050 and 3000 refers
to the same chunk. If the user application is run in the unaligned
chunks mode, then the incoming addr will be left untouched.
UMEM Completion Ring
~~~~~~~~~~~~~~~~~~~~
The COMPLETION Ring is used transfer ownership of UMEM frames from
kernel-space to user-space. Just like the FILL ring, UMEM indices are
used.
Frames passed from the kernel to user-space are frames that has been
sent (TX ring) and can be used by user-space again.
The user application consumes UMEM addrs from this ring.
RX Ring
~~~~~~~
The RX ring is the receiving side of a socket. Each entry in the ring
is a struct xdp_desc descriptor. The descriptor contains UMEM offset
(addr) and the length of the data (len).
If no frames have been passed to kernel via the FILL ring, no
descriptors will (or can) appear on the RX ring.
The user application consumes struct xdp_desc descriptors from this
ring.
TX Ring
~~~~~~~
The TX ring is used to send frames. The struct xdp_desc descriptor is
filled (index, length and offset) and passed into the ring.
To start the transfer a sendmsg() system call is required. This might
be relaxed in the future.
The user application produces struct xdp_desc descriptors to this
ring.
Libbpf
======
Libbpf is a helper library for eBPF and XDP that makes using these
technologies a lot simpler. It also contains specific helper functions
in tools/testing/selftests/bpf/xsk.h for facilitating the use of
AF_XDP. It contains two types of functions: those that can be used to
make the setup of AF_XDP socket easier and ones that can be used in the
data plane to access the rings safely and quickly.
We recommend that you use this library unless you have become a power
user. It will make your program a lot simpler.
XSKMAP / BPF_MAP_TYPE_XSKMAP
============================
On XDP side there is a BPF map type BPF_MAP_TYPE_XSKMAP (XSKMAP) that
is used in conjunction with bpf_redirect_map() to pass the ingress
frame to a socket.
The user application inserts the socket into the map, via the bpf()
system call.
Note that if an XDP program tries to redirect to a socket that does
not match the queue configuration and netdev, the frame will be
dropped. E.g. an AF_XDP socket is bound to netdev eth0 and
queue 17. Only the XDP program executing for eth0 and queue 17 will
successfully pass data to the socket. Please refer to the sample
application (samples/bpf/) in for an example.
Configuration Flags and Socket Options
======================================
These are the various configuration flags that can be used to control
and monitor the behavior of AF_XDP sockets.
XDP_COPY and XDP_ZEROCOPY bind flags
------------------------------------
When you bind to a socket, the kernel will first try to use zero-copy
copy. If zero-copy is not supported, it will fall back on using copy
mode, i.e. copying all packets out to user space. But if you would
like to force a certain mode, you can use the following flags. If you
pass the XDP_COPY flag to the bind call, the kernel will force the
socket into copy mode. If it cannot use copy mode, the bind call will
fail with an error. Conversely, the XDP_ZEROCOPY flag will force the
socket into zero-copy mode or fail.
XDP_SHARED_UMEM bind flag
-------------------------
This flag enables you to bind multiple sockets to the same UMEM. It
works on the same queue id, between queue ids and between
netdevs/devices. In this mode, each socket has their own RX and TX
rings as usual, but you are going to have one or more FILL and
COMPLETION ring pairs. You have to create one of these pairs per
unique netdev and queue id tuple that you bind to.
Starting with the case were we would like to share a UMEM between
sockets bound to the same netdev and queue id. The UMEM (tied to the
fist socket created) will only have a single FILL ring and a single
COMPLETION ring as there is only on unique netdev,queue_id tuple that
we have bound to. To use this mode, create the first socket and bind
it in the normal way. Create a second socket and create an RX and a TX
ring, or at least one of them, but no FILL or COMPLETION rings as the
ones from the first socket will be used. In the bind call, set he
XDP_SHARED_UMEM option and provide the initial socket's fd in the
sxdp_shared_umem_fd field. You can attach an arbitrary number of extra
sockets this way.
What socket will then a packet arrive on? This is decided by the XDP
program. Put all the sockets in the XSK_MAP and just indicate which
index in the array you would like to send each packet to. A simple
round-robin example of distributing packets is shown below:
.. code-block:: c
#include <linux/bpf.h>
#include "bpf_helpers.h"
#define MAX_SOCKS 16
struct {
__uint(type, BPF_MAP_TYPE_XSKMAP);
__uint(max_entries, MAX_SOCKS);
__uint(key_size, sizeof(int));
__uint(value_size, sizeof(int));
} xsks_map SEC(".maps");
static unsigned int rr;
SEC("xdp_sock") int xdp_sock_prog(struct xdp_md *ctx)
{
rr = (rr + 1) & (MAX_SOCKS - 1);
return bpf_redirect_map(&xsks_map, rr, XDP_DROP);
}
Note, that since there is only a single set of FILL and COMPLETION
rings, and they are single producer, single consumer rings, you need
to make sure that multiple processes or threads do not use these rings
concurrently. There are no synchronization primitives in the
libbpf code that protects multiple users at this point in time.
Libbpf uses this mode if you create more than one socket tied to the
same UMEM. However, note that you need to supply the
XSK_LIBBPF_FLAGS__INHIBIT_PROG_LOAD libbpf_flag with the
xsk_socket__create calls and load your own XDP program as there is no
built in one in libbpf that will route the traffic for you.
The second case is when you share a UMEM between sockets that are
bound to different queue ids and/or netdevs. In this case you have to
create one FILL ring and one COMPLETION ring for each unique
netdev,queue_id pair. Let us say you want to create two sockets bound
to two different queue ids on the same netdev. Create the first socket
and bind it in the normal way. Create a second socket and create an RX
and a TX ring, or at least one of them, and then one FILL and
COMPLETION ring for this socket. Then in the bind call, set he
XDP_SHARED_UMEM option and provide the initial socket's fd in the
sxdp_shared_umem_fd field as you registered the UMEM on that
socket. These two sockets will now share one and the same UMEM.
There is no need to supply an XDP program like the one in the previous
case where sockets were bound to the same queue id and
device. Instead, use the NIC's packet steering capabilities to steer
the packets to the right queue. In the previous example, there is only
one queue shared among sockets, so the NIC cannot do this steering. It
can only steer between queues.
In libbpf, you need to use the xsk_socket__create_shared() API as it
takes a reference to a FILL ring and a COMPLETION ring that will be
created for you and bound to the shared UMEM. You can use this
function for all the sockets you create, or you can use it for the
second and following ones and use xsk_socket__create() for the first
one. Both methods yield the same result.
Note that a UMEM can be shared between sockets on the same queue id
and device, as well as between queues on the same device and between
devices at the same time.
XDP_USE_NEED_WAKEUP bind flag
-----------------------------
This option adds support for a new flag called need_wakeup that is
present in the FILL ring and the TX ring, the rings for which user
space is a producer. When this option is set in the bind call, the
need_wakeup flag will be set if the kernel needs to be explicitly
woken up by a syscall to continue processing packets. If the flag is
zero, no syscall is needed.
If the flag is set on the FILL ring, the application needs to call
poll() to be able to continue to receive packets on the RX ring. This
can happen, for example, when the kernel has detected that there are no
more buffers on the FILL ring and no buffers left on the RX HW ring of
the NIC. In this case, interrupts are turned off as the NIC cannot
receive any packets (as there are no buffers to put them in), and the
need_wakeup flag is set so that user space can put buffers on the
FILL ring and then call poll() so that the kernel driver can put these
buffers on the HW ring and start to receive packets.
If the flag is set for the TX ring, it means that the application
needs to explicitly notify the kernel to send any packets put on the
TX ring. This can be accomplished either by a poll() call, as in the
RX path, or by calling sendto().
An example with the use of libbpf helpers would look like this for the
TX path:
.. code-block:: c
if (xsk_ring_prod__needs_wakeup(&my_tx_ring))
sendto(xsk_socket__fd(xsk_handle), NULL, 0, MSG_DONTWAIT, NULL, 0);
I.e., only use the syscall if the flag is set.
We recommend that you always enable this mode as it usually leads to
better performance especially if you run the application and the
driver on the same core, but also if you use different cores for the
application and the kernel driver, as it reduces the number of
syscalls needed for the TX path.
XDP_{RX|TX|UMEM_FILL|UMEM_COMPLETION}_RING setsockopts
------------------------------------------------------
These setsockopts sets the number of descriptors that the RX, TX,
FILL, and COMPLETION rings respectively should have. It is mandatory
to set the size of at least one of the RX and TX rings. If you set
both, you will be able to both receive and send traffic from your
application, but if you only want to do one of them, you can save
resources by only setting up one of them. Both the FILL ring and the
COMPLETION ring are mandatory as you need to have a UMEM tied to your
socket. But if the XDP_SHARED_UMEM flag is used, any socket after the
first one does not have a UMEM and should in that case not have any
FILL or COMPLETION rings created as the ones from the shared UMEM will
be used. Note, that the rings are single-producer single-consumer, so
do not try to access them from multiple processes at the same
time. See the XDP_SHARED_UMEM section.
In libbpf, you can create Rx-only and Tx-only sockets by supplying
NULL to the rx and tx arguments, respectively, to the
xsk_socket__create function.
If you create a Tx-only socket, we recommend that you do not put any
packets on the fill ring. If you do this, drivers might think you are
going to receive something when you in fact will not, and this can
negatively impact performance.
XDP_UMEM_REG setsockopt
-----------------------
This setsockopt registers a UMEM to a socket. This is the area that
contain all the buffers that packet can reside in. The call takes a
pointer to the beginning of this area and the size of it. Moreover, it
also has parameter called chunk_size that is the size that the UMEM is
divided into. It can only be 2K or 4K at the moment. If you have an
UMEM area that is 128K and a chunk size of 2K, this means that you
will be able to hold a maximum of 128K / 2K = 64 packets in your UMEM
area and that your largest packet size can be 2K.
There is also an option to set the headroom of each single buffer in
the UMEM. If you set this to N bytes, it means that the packet will
start N bytes into the buffer leaving the first N bytes for the
application to use. The final option is the flags field, but it will
be dealt with in separate sections for each UMEM flag.
SO_BINDTODEVICE setsockopt
--------------------------
This is a generic SOL_SOCKET option that can be used to tie AF_XDP
socket to a particular network interface. It is useful when a socket
is created by a privileged process and passed to a non-privileged one.
Once the option is set, kernel will refuse attempts to bind that socket
to a different interface. Updating the value requires CAP_NET_RAW.
XDP_MAX_TX_SKB_BUDGET setsockopt
--------------------------------
This setsockopt sets the maximum number of descriptors that can be handled
and passed to the driver at one send syscall. It is applied in the copy
mode to allow application to tune the per-socket maximum iteration for
better throughput and less frequency of send syscall.
Allowed range is [32, xs->tx->nentries].
XDP_STATISTICS getsockopt
-------------------------
Gets drop statistics of a socket that can be useful for debug
purposes. The supported statistics are shown below:
.. code-block:: c
struct xdp_statistics {
__u64 rx_dropped; /* Dropped for reasons other than invalid desc */
__u64 rx_invalid_descs; /* Dropped due to invalid descriptor */
__u64 tx_invalid_descs; /* Dropped due to invalid descriptor */
};
XDP_OPTIONS getsockopt
----------------------
Gets options from an XDP socket. The only one supported so far is
XDP_OPTIONS_ZEROCOPY which tells you if zero-copy is on or not.
Multi-Buffer Support
====================
With multi-buffer support, programs using AF_XDP sockets can receive
and transmit packets consisting of multiple buffers both in copy and
zero-copy mode. For example, a packet can consist of two
frames/buffers, one with the header and the other one with the data,
or a 9K Ethernet jumbo frame can be constructed by chaining together
three 4K frames.
Some definitions:
* A packet consists of one or more frames
* A descriptor in one of the AF_XDP rings always refers to a single
frame. In the case the packet consists of a single frame, the
descriptor refers to the whole packet.
To enable multi-buffer support for an AF_XDP socket, use the new bind
flag XDP_USE_SG. If this is not provided, all multi-buffer packets
will be dropped just as before. Note that the XDP program loaded also
needs to be in multi-buffer mode. This can be accomplished by using
"xdp.frags" as the section name of the XDP program used.
To represent a packet consisting of multiple frames, a new flag called
XDP_PKT_CONTD is introduced in the options field of the Rx and Tx
descriptors. If it is true (1) the packet continues with the next
descriptor and if it is false (0) it means this is the last descriptor
of the packet. Why the reverse logic of end-of-packet (eop) flag found
in many NICs? Just to preserve compatibility with non-multi-buffer
applications that have this bit set to false for all packets on Rx,
and the apps set the options field to zero for Tx, as anything else
will be treated as an invalid descriptor.
These are the semantics for producing packets onto AF_XDP Tx ring
consisting of multiple frames:
* When an invalid descriptor is found, all the other
descriptors/frames of this packet are marked as invalid and not
completed. The next descriptor is treated as the start of a new
packet, even if this was not the intent (because we cannot guess
the intent). As before, if your program is producing invalid
descriptors you have a bug that must be fixed.
* Zero length descriptors are treated as invalid descriptors.
* For copy mode, the maximum supported number of frames in a packet is
equal to CONFIG_MAX_SKB_FRAGS + 1. If it is exceeded, all
descriptors accumulated so far are dropped and treated as
invalid. To produce an application that will work on any system
regardless of this config setting, limit the number of frags to 18,
as the minimum value of the config is 17.
* For zero-copy mode, the limit is up to what the NIC HW
supports. Usually at least five on the NICs we have checked. We
consciously chose to not enforce a rigid limit (such as
CONFIG_MAX_SKB_FRAGS + 1) for zero-copy mode, as it would have
resulted in copy actions under the hood to fit into what limit the
NIC supports. Kind of defeats the purpose of zero-copy mode. How to
probe for this limit is explained in the "probe for multi-buffer
support" section.
On the Rx path in copy-mode, the xsk core copies the XDP data into
multiple descriptors, if needed, and sets the XDP_PKT_CONTD flag as
detailed before. Zero-copy mode works the same, though the data is not
copied. When the application gets a descriptor with the XDP_PKT_CONTD
flag set to one, it means that the packet consists of multiple buffers
and it continues with the next buffer in the following
descriptor. When a descriptor with XDP_PKT_CONTD == 0 is received, it
means that this is the last buffer of the packet. AF_XDP guarantees
that only a complete packet (all frames in the packet) is sent to the
application. If there is not enough space in the AF_XDP Rx ring, all
frames of the packet will be dropped.
If application reads a batch of descriptors, using for example the libxdp
interfaces, it is not guaranteed that the batch will end with a full
packet. It might end in the middle of a packet and the rest of the
buffers of that packet will arrive at the beginning of the next batch,
since the libxdp interface does not read the whole ring (unless you
have an enormous batch size or a very small ring size).
An example program each for Rx and Tx multi-buffer support can be found
later in this document.
Usage
-----
In order to use AF_XDP sockets two parts are needed. The user-space
application and the XDP program. For a complete setup and usage example,
please refer to the xdp-project at
https://github.com/xdp-project/bpf-examples/tree/main/AF_XDP-example.
The XDP code sample is the following:
.. code-block:: c
SEC("xdp_sock") int xdp_sock_prog(struct xdp_md *ctx)
{
int index = ctx->rx_queue_index;
// A set entry here means that the corresponding queue_id
// has an active AF_XDP socket bound to it.
if (bpf_map_lookup_elem(&xsks_map, &index))
return bpf_redirect_map(&xsks_map, index, 0);
return XDP_PASS;
}
A simple but not so performance ring dequeue and enqueue could look
like this:
.. code-block:: c
// struct xdp_rxtx_ring {
// __u32 *producer;
// __u32 *consumer;
// struct xdp_desc *desc;
// };
// struct xdp_umem_ring {
// __u32 *producer;
// __u32 *consumer;
// __u64 *desc;
// };
// typedef struct xdp_rxtx_ring RING;
// typedef struct xdp_umem_ring RING;
// typedef struct xdp_desc RING_TYPE;
// typedef __u64 RING_TYPE;
int dequeue_one(RING *ring, RING_TYPE *item)
{
__u32 entries = *ring->producer - *ring->consumer;
if (entries == 0)
return -1;
// read-barrier!
*item = ring->desc[*ring->consumer & (RING_SIZE - 1)];
(*ring->consumer)++;
return 0;
}
int enqueue_one(RING *ring, const RING_TYPE *item)
{
u32 free_entries = RING_SIZE - (*ring->producer - *ring->consumer);
if (free_entries == 0)
return -1;
ring->desc[*ring->producer & (RING_SIZE - 1)] = *item;
// write-barrier!
(*ring->producer)++;
return 0;
}
But please use the libbpf functions as they are optimized and ready to
use. Will make your life easier.
Usage Multi-Buffer Rx
---------------------
Here is a simple Rx path pseudo-code example (using libxdp interfaces
for simplicity). Error paths have been excluded to keep it short:
.. code-block:: c
void rx_packets(struct xsk_socket_info *xsk)
{
static bool new_packet = true;
u32 idx_rx = 0, idx_fq = 0;
static char *pkt;
int rcvd = xsk_ring_cons__peek(&xsk->rx, opt_batch_size, &idx_rx);
xsk_ring_prod__reserve(&xsk->umem->fq, rcvd, &idx_fq);
for (int i = 0; i < rcvd; i++) {
struct xdp_desc *desc = xsk_ring_cons__rx_desc(&xsk->rx, idx_rx++);
char *frag = xsk_umem__get_data(xsk->umem->buffer, desc->addr);
bool eop = !(desc->options & XDP_PKT_CONTD);
if (new_packet)
pkt = frag;
else
add_frag_to_pkt(pkt, frag);
if (eop)
process_pkt(pkt);
new_packet = eop;
*xsk_ring_prod__fill_addr(&xsk->umem->fq, idx_fq++) = desc->addr;
}
xsk_ring_prod__submit(&xsk->umem->fq, rcvd);
xsk_ring_cons__release(&xsk->rx, rcvd);
}
Usage Multi-Buffer Tx
---------------------
Here is an example Tx path pseudo-code (using libxdp interfaces for
simplicity) ignoring that the umem is finite in size, and that we
eventually will run out of packets to send. Also assumes pkts.addr
points to a valid location in the umem.
.. code-block:: c
void tx_packets(struct xsk_socket_info *xsk, struct pkt *pkts,
int batch_size)
{
u32 idx, i, pkt_nb = 0;
xsk_ring_prod__reserve(&xsk->tx, batch_size, &idx);
for (i = 0; i < batch_size;) {
u64 addr = pkts[pkt_nb].addr;
u32 len = pkts[pkt_nb].size;
do {
struct xdp_desc *tx_desc;
tx_desc = xsk_ring_prod__tx_desc(&xsk->tx, idx + i++);
tx_desc->addr = addr;
if (len > xsk_frame_size) {
tx_desc->len = xsk_frame_size;
tx_desc->options = XDP_PKT_CONTD;
} else {
tx_desc->len = len;
tx_desc->options = 0;
pkt_nb++;
}
len -= tx_desc->len;
addr += xsk_frame_size;
if (i == batch_size) {
/* Remember len, addr, pkt_nb for next iteration.
* Skipped for simplicity.
*/
break;
}
} while (len);
}
xsk_ring_prod__submit(&xsk->tx, i);
}
Probing for Multi-Buffer Support
--------------------------------
To discover if a driver supports multi-buffer AF_XDP in SKB or DRV
mode, use the XDP_FEATURES feature of netlink in linux/netdev.h to
query for NETDEV_XDP_ACT_RX_SG support. This is the same flag as for
querying for XDP multi-buffer support. If XDP supports multi-buffer in
a driver, then AF_XDP will also support that in SKB and DRV mode.
To discover if a driver supports multi-buffer AF_XDP in zero-copy
mode, use XDP_FEATURES and first check the NETDEV_XDP_ACT_XSK_ZEROCOPY
flag. If it is set, it means that at least zero-copy is supported and
you should go and check the netlink attribute
NETDEV_A_DEV_XDP_ZC_MAX_SEGS in linux/netdev.h. An unsigned integer
value will be returned stating the max number of frags that are
supported by this device in zero-copy mode. These are the possible
return values:
1: Multi-buffer for zero-copy is not supported by this device, as max
one fragment supported means that multi-buffer is not possible.
>=2: Multi-buffer is supported in zero-copy mode for this device. The
returned number signifies the max number of frags supported.
For an example on how these are used through libbpf, please take a
look at tools/testing/selftests/bpf/xskxceiver.c.
Multi-Buffer Support for Zero-Copy Drivers
------------------------------------------
Zero-copy drivers usually use the batched APIs for Rx and Tx
processing. Note that the Tx batch API guarantees that it will provide
a batch of Tx descriptors that ends with full packet at the end. This
to facilitate extending a zero-copy driver with multi-buffer support.
Sample application
==================
There is a xdpsock benchmarking/test application that can be found at
https://github.com/xdp-project/bpf-examples/tree/main/AF_XDP-example
that demonstrates how to use AF_XDP sockets with private
UMEMs. Say that you would like your UDP traffic from port 4242 to end
up in queue 16, that we will enable AF_XDP on. Here, we use ethtool
for this::
ethtool -N p3p2 rx-flow-hash udp4 fn
ethtool -N p3p2 flow-type udp4 src-port 4242 dst-port 4242 \
action 16
Running the rxdrop benchmark in XDP_DRV mode can then be done
using::
samples/bpf/xdpsock -i p3p2 -q 16 -r -N
For XDP_SKB mode, use the switch "-S" instead of "-N" and all options
can be displayed with "-h", as usual.
This sample application uses libbpf to make the setup and usage of
AF_XDP simpler. If you want to know how the raw uapi of AF_XDP is
really used to make something more advanced, take a look at the libbpf
code in tools/testing/selftests/bpf/xsk.[ch].
FAQ
=======
Q: I am not seeing any traffic on the socket. What am I doing wrong?
A: When a netdev of a physical NIC is initialized, Linux usually
allocates one RX and TX queue pair per core. So on a 8 core system,
queue ids 0 to 7 will be allocated, one per core. In the AF_XDP
bind call or the xsk_socket__create libbpf function call, you
specify a specific queue id to bind to and it is only the traffic
towards that queue you are going to get on you socket. So in the
example above, if you bind to queue 0, you are NOT going to get any
traffic that is distributed to queues 1 through 7. If you are
lucky, you will see the traffic, but usually it will end up on one
of the queues you have not bound to.
There are a number of ways to solve the problem of getting the
traffic you want to the queue id you bound to. If you want to see
all the traffic, you can force the netdev to only have 1 queue, queue
id 0, and then bind to queue 0. You can use ethtool to do this::
sudo ethtool -L <interface> combined 1
If you want to only see part of the traffic, you can program the
NIC through ethtool to filter out your traffic to a single queue id
that you can bind your XDP socket to. Here is one example in which
UDP traffic to and from port 4242 are sent to queue 2::
sudo ethtool -N <interface> rx-flow-hash udp4 fn
sudo ethtool -N <interface> flow-type udp4 src-port 4242 dst-port \
4242 action 2
A number of other ways are possible all up to the capabilities of
the NIC you have.
Q: Can I use the XSKMAP to implement a switch between different umems
in copy mode?
A: The short answer is no, that is not supported at the moment. The
XSKMAP can only be used to switch traffic coming in on queue id X
to sockets bound to the same queue id X. The XSKMAP can contain
sockets bound to different queue ids, for example X and Y, but only
traffic goming in from queue id Y can be directed to sockets bound
to the same queue id Y. In zero-copy mode, you should use the
switch, or other distribution mechanism, in your NIC to direct
traffic to the correct queue id and socket.
Q: My packets are sometimes corrupted. What is wrong?
A: Care has to be taken not to feed the same buffer in the UMEM into
more than one ring at the same time. If you for example feed the
same buffer into the FILL ring and the TX ring at the same time, the
NIC might receive data into the buffer at the same time it is
sending it. This will cause some packets to become corrupted. Same
thing goes for feeding the same buffer into the FILL rings
belonging to different queue ids or netdevs bound with the
XDP_SHARED_UMEM flag.
Credits
=======
- Björn Töpel (AF_XDP core)
- Magnus Karlsson (AF_XDP core)
- Alexander Duyck
- Alexei Starovoitov
- Daniel Borkmann
- Jesper Dangaard Brouer
- John Fastabend
- Jonathan Corbet (LWN coverage)
- Michael S. Tsirkin
- Qi Z Zhang
- Willem de Bruijn
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
개요
1-89`.. SPDX-License-Identifier: GPL-2.0`
AF_XDP
개요
AF_XDP는 고성능 패킷 처리에 최적화된 address family입니다.
이 문서는 독자가 BPF와 XDP에 익숙하다고 가정합니다. 그렇지 않다면 Cilium 프로젝트의 참고 안내서 `http://cilium.readthedocs.io/en/latest/bpf/`를 참조하십시오.
XDP 프로그램은 `XDP_REDIRECT` action과 `bpf_redirect_map()` 함수를 사용해 ingress frame을 다른 XDP 지원 netdev로 redirect할 수 있습니다. AF_XDP socket은 XDP 프로그램이 frame을 사용자 공간 애플리케이션의 memory buffer로 redirect할 수 있게 합니다.
AF_XDP socket(XSK)은 일반 `socket()` system call로 만듭니다. 각 XSK에는 RX ring과 TX ring이 연결됩니다. Socket은 RX ring으로 패킷을 받고 TX ring으로 패킷을 보냅니다. 두 ring은 각각 `XDP_RX_RING`, `XDP_TX_RING` setsockopt로 등록하고 크기를 정하며, 각 socket에는 이 둘 중 적어도 하나가 반드시 있어야 합니다.
RX 또는 TX descriptor ring은 UMEM이라는 memory 영역의 data buffer를 가리킵니다. RX와 TX는 같은 UMEM을 공유할 수 있어 RX와 TX 사이에서 패킷을 복사하지 않아도 됩니다. 재전송 가능성 때문에 패킷을 잠시 보관해야 할 때도 그 패킷을 가리키는 descriptor를 다른 frame을 가리키도록 바꿔 즉시 재사용할 수 있으므로 data copy를 다시 피할 수 있습니다.
UMEM은 크기가 같은 여러 chunk로 이루어집니다. Ring의 descriptor는 `addr`로 frame을 참조하며, 이 값은 전체 UMEM 영역 안의 offset입니다. 사용자 공간은 `malloc`, `mmap`, huge page 등 적절한 방법으로 UMEM memory를 할당하고 `XDP_UMEM_REG` setsockopt로 kernel에 등록합니다.
UMEM에도 FILL ring과 COMPLETION ring이 있습니다. 애플리케이션은 kernel이 RX 패킷 data를 채울 frame의 `addr`를 FILL ring으로 보냅니다. 패킷을 받으면 해당 frame 참조가 RX ring에 나타납니다. COMPLETION ring에는 kernel이 송신을 완전히 끝내 사용자 공간이 TX 또는 RX에 다시 쓸 수 있는 frame `addr`가 들어갑니다. 따라서 RX 경로는 FILL·RX ring을, TX 경로는 TX·COMPLETION ring을 사용합니다.
마지막으로 `bind()`를 호출해 socket을 장치의 특정 queue id에 묶습니다. `bind()`가 끝나야 traffic이 흐르기 시작합니다.
원한다면 여러 process가 UMEM을 공유할 수 있습니다. 새 process는 UMEM과 두 UMEM ring 등록을 생략하고 `bind` 호출에 `XDP_SHARED_UMEM` flag와 공유 대상 process의 XSK를 지정한 뒤 자신의 새 XSK를 전달합니다. 그러면 새 process의 RX ring에 공유 UMEM을 가리키는 frame `addr`가 들어옵니다.
Ring 구조는 성능을 위해 single-consumer/single-producer이므로 새 process는 다른 process와 RX·TX ring을 공유할 수 없고 자기 socket과 RX·TX ring을 만들어야 합니다. 같은 이유로 UMEM 하나에는 FILL·COMPLETION ring 집합이 하나뿐이며, 단일 process가 UMEM을 처리할 책임을 집니다.
XDP 프로그램에서 XSK로 패킷을 분배할 때는 `XSKMAP`, 정식 이름으로 `BPF_MAP_TYPE_XSKMAP`을 사용합니다. 사용자 공간 애플리케이션은 map의 임의 index에 XSK를 넣고, XDP 프로그램은 특정 index로 패킷을 redirect합니다. XDP는 그 XSK가 해당 장치와 queue 번호에 실제로 bind되었는지 검증합니다. 일치하지 않거나 해당 index가 비어 있으면 패킷을 drop합니다. 따라서 현재 XSK를 통해 사용자 공간으로 traffic을 받으려면 XDP 프로그램이 load되어 있고 XSKMAP에 적어도 하나의 XSK가 있어야 합니다.
AF_XDP는 `XDP_SKB`와 `XDP_DRV` 두 mode로 동작할 수 있습니다. Driver가 XDP를 지원하지 않거나 XDP 프로그램을 load할 때 `XDP_SKB`를 명시하면 generic XDP와 SKB를 사용하고 data를 사용자 공간으로 복사하는 fallback mode가 작동합니다. Driver가 XDP를 지원하면 AF_XDP가 이를 사용해 성능을 높이지만, 이 경우에도 사용자 공간으로의 data copy는 남아 있습니다.
.. SPDX-License-Identifier: GPL-2.0
======
AF_XDP
======
Overview
========
AF_XDP is an address family that is optimized for high performance
packet processing.
This document assumes that the reader is familiar with BPF and XDP. If
not, the Cilium project has an excellent reference guide at
http://cilium.readthedocs.io/en/latest/bpf/.
Using the XDP_REDIRECT action from an XDP program, the program can
redirect ingress frames to other XDP enabled netdevs, using the
bpf_redirect_map() function. AF_XDP sockets enable the possibility for
XDP programs to redirect frames to a memory buffer in a user-space
application.
An AF_XDP socket (XSK) is created with the normal socket()
syscall. Associated with each XSK are two rings: the RX ring and the
TX ring. A socket can receive packets on the RX ring and it can send
packets on the TX ring. These rings are registered and sized with the
setsockopts XDP_RX_RING and XDP_TX_RING, respectively. It is mandatory
to have at least one of these rings for each socket. An RX or TX
descriptor ring points to a data buffer in a memory area called a
UMEM. RX and TX can share the same UMEM so that a packet does not have
to be copied between RX and TX. Moreover, if a packet needs to be kept
for a while due to a possible retransmit, the descriptor that points
to that packet can be changed to point to another and reused right
away. This again avoids copying data.
The UMEM consists of a number of equally sized chunks. A descriptor in
one of the rings references a frame by referencing its addr. The addr
is simply an offset within the entire UMEM region. The user space
allocates memory for this UMEM using whatever means it feels is most
appropriate (malloc, mmap, huge pages, etc). This memory area is then
registered with the kernel using the new setsockopt XDP_UMEM_REG. The
UMEM also has two rings: the FILL ring and the COMPLETION ring. The
FILL ring is used by the application to send down addr for the kernel
to fill in with RX packet data. References to these frames will then
appear in the RX ring once each packet has been received. The
COMPLETION ring, on the other hand, contains frame addr that the
kernel has transmitted completely and can now be used again by user
space, for either TX or RX. Thus, the frame addrs appearing in the
COMPLETION ring are addrs that were previously transmitted using the
TX ring. In summary, the RX and FILL rings are used for the RX path
and the TX and COMPLETION rings are used for the TX path.
The socket is then finally bound with a bind() call to a device and a
specific queue id on that device, and it is not until bind is
completed that traffic starts to flow.
The UMEM can be shared between processes, if desired. If a process
wants to do this, it simply skips the registration of the UMEM and its
corresponding two rings, sets the XDP_SHARED_UMEM flag in the bind
call and submits the XSK of the process it would like to share UMEM
with as well as its own newly created XSK socket. The new process will
then receive frame addr references in its own RX ring that point to
this shared UMEM. Note that since the ring structures are
single-consumer / single-producer (for performance reasons), the new
process has to create its own socket with associated RX and TX rings,
since it cannot share this with the other process. This is also the
reason that there is only one set of FILL and COMPLETION rings per
UMEM. It is the responsibility of a single process to handle the UMEM.
How is then packets distributed from an XDP program to the XSKs? There
is a BPF map called XSKMAP (or BPF_MAP_TYPE_XSKMAP in full). The
user-space application can place an XSK at an arbitrary place in this
map. The XDP program can then redirect a packet to a specific index in
this map and at this point XDP validates that the XSK in that map was
indeed bound to that device and ring number. If not, the packet is
dropped. If the map is empty at that index, the packet is also
dropped. This also means that it is currently mandatory to have an XDP
program loaded (and one XSK in the XSKMAP) to be able to get any
traffic to user space through the XSK.
AF_XDP can operate in two different modes: XDP_SKB and XDP_DRV. If the
driver does not have support for XDP, or XDP_SKB is explicitly chosen
when loading the XDP program, XDP_SKB mode is employed that uses SKBs
together with the generic XDP support and copies out the data to user
space. A fallback mode that works for any network device. On the other
hand, if the driver has support for XDP, it will be used by the AF_XDP
code to provide better performance, but there is still a copy of the
data into user space.
개념
90-104개념
AF_XDP socket을 사용하려면 여러 관련 object를 설정해야 합니다. 다음 절에서 각 object와 option을 설명합니다.
AF_XDP 동작의 전체 개요는 2018 Linux Plumbers 논문 `http://vger.kernel.org/lpc_net2018_talks/lpc18_paper_af_xdp_perf-v2.pdf`에서도 볼 수 있습니다. AF_XDP의 첫 시도였던 2017년 `AF_PACKET v4` 논문은 이후 거의 모든 내용이 바뀌었으므로 참조하지 마십시오. Jonathan Corbet의 LWN 기사 `Accelerating networking with AF_XDP`도 `https://lwn.net/Articles/750845/`에서 볼 수 있습니다.
Concepts
========
In order to use an AF_XDP socket, a number of associated objects need
to be setup. These objects and their options are explained in the
following sections.
For an overview on how AF_XDP works, you can also take a look at the
Linux Plumbers paper from 2018 on the subject:
http://vger.kernel.org/lpc_net2018_talks/lpc18_paper_af_xdp_perf-v2.pdf. Do
NOT consult the paper from 2017 on "AF_PACKET v4", the first attempt
at AF_XDP. Nearly everything changed since then. Jonathan Corbet has
also written an excellent article on LWN, "Accelerating networking
with AF_XDP". It can be found at https://lwn.net/Articles/750845/.
UMEM
105-124UMEM
UMEM은 가상 주소상 연속인 memory 영역이며 크기가 같은 frame으로 나뉩니다. UMEM은 netdev와 그 netdev의 특정 queue id에 연결됩니다. `XDP_UMEM_REG` setsockopt system call로 chunk 크기, headroom, 시작 주소, 전체 크기를 정해 만들고 구성하며, `bind()` system call로 netdev와 queue id에 묶습니다.
AF_XDP socket 하나는 UMEM 하나에 연결되지만 UMEM 하나에는 여러 AF_XDP socket이 연결될 수 있습니다. Socket A로 만든 UMEM을 socket B에서 공유하려면 `struct sockaddr_xdp`의 `sxdp_flags` member에 `XDP_SHARED_UMEM`을 설정하고, A의 file descriptor를 `sxdp_shared_umem_fd` member로 전달합니다.
UMEM에는 kernel과 사용자 공간 애플리케이션 사이에서 UMEM frame 소유권을 넘기는 single-producer/single-consumer ring 두 개가 있습니다.
UMEM
----
UMEM is a region of virtual contiguous memory, divided into
equal-sized frames. An UMEM is associated to a netdev and a specific
queue id of that netdev. It is created and configured (chunk size,
headroom, start address and size) by using the XDP_UMEM_REG setsockopt
system call. A UMEM is bound to a netdev and queue id, via the bind()
system call.
An AF_XDP is socket linked to a single UMEM, but one UMEM can have
multiple AF_XDP sockets. To share an UMEM created via one socket A,
the next socket B can do this by setting the XDP_SHARED_UMEM flag in
struct sockaddr_xdp member sxdp_flags, and passing the file descriptor
of A to struct sockaddr_xdp member sxdp_shared_umem_fd.
The UMEM has two single-producer/single-consumer rings that are used
to transfer ownership of UMEM frames between the kernel and the
user-space application.
Ring 공통 구조
125-150Ring
Ring은 FILL, COMPLETION, RX, TX 네 종류입니다. 모두 single-producer/single-consumer이므로 여러 process나 thread가 ring을 읽고 쓸 때 사용자 공간 애플리케이션이 명시적으로 동기화해야 합니다.
UMEM은 FILL·COMPLETION ring을 사용합니다. UMEM에 연결된 각 socket에는 RX queue, TX queue 또는 둘 다 있어야 합니다. 예를 들어 네 socket이 모두 TX와 RX를 수행하면 FILL ring 하나, COMPLETION ring 하나, TX ring 네 개, RX ring 네 개가 존재합니다.
Ring은 head(producer)/tail(consumer) index를 사용합니다. Producer는 `struct xdp_ring`의 `producer` member가 가리키는 index에 data를 쓰고 producer index를 증가시킵니다. Consumer는 `consumer` member가 가리키는 index에서 data를 읽고 consumer index를 증가시킵니다.
Ring은 `_RING` setsockopt system call로 구성·생성하고, `XDP_PGOFF_RX_RING`, `XDP_PGOFF_TX_RING`, `XDP_UMEM_PGOFF_FILL_RING`, `XDP_UMEM_PGOFF_COMPLETION_RING` offset을 사용해 `mmap()`으로 사용자 공간에 mapping합니다.
Ring 크기는 2의 거듭제곱이어야 합니다.
Rings
-----
There are a four different kind of rings: FILL, COMPLETION, RX and
TX. All rings are single-producer/single-consumer, so the user-space
application need explicit synchronization of multiple
processes/threads are reading/writing to them.
The UMEM uses two rings: FILL and COMPLETION. Each socket associated
with the UMEM must have an RX queue, TX queue or both. Say, that there
is a setup with four sockets (all doing TX and RX). Then there will be
one FILL ring, one COMPLETION ring, four TX rings and four RX rings.
The rings are head(producer)/tail(consumer) based rings. A producer
writes the data ring at the index pointed out by struct xdp_ring
producer member, and increasing the producer index. A consumer reads
the data ring at the index pointed out by struct xdp_ring consumer
member, and increasing the consumer index.
The rings are configured and created via the _RING setsockopt system
calls and mmapped to user-space using the appropriate offset to mmap()
(XDP_PGOFF_RX_RING, XDP_PGOFF_TX_RING, XDP_UMEM_PGOFF_FILL_RING and
XDP_UMEM_PGOFF_COMPLETION_RING).
The size of the rings need to be of size power of two.
UMEM FILL ring
151-168UMEM FILL ring
FILL ring은 UMEM frame의 소유권을 사용자 공간에서 kernel 공간으로 넘기며 ring에는 UMEM `addr`를 넣습니다. 예를 들어 UMEM이 64 KiB이고 chunk가 4 KiB이면 UMEM에는 chunk 16개가 있고 0부터 64 KiB 사이의 `addr`를 전달할 수 있습니다.
Kernel에 넘긴 frame은 ingress 경로, 즉 RX ring에서 사용합니다.
사용자 애플리케이션이 이 ring에 UMEM `addr`를 생산합니다. Aligned chunk mode에서는 kernel이 들어온 `addr`를 mask합니다. Chunk 크기가 2 KiB라면 `log2(2048)`개의 최하위 bit를 지우므로 2048, 2050, 3000은 같은 chunk를 가리킵니다. Unaligned chunk mode에서는 들어온 `addr`를 바꾸지 않습니다.
UMEM Fill Ring
~~~~~~~~~~~~~~
The FILL ring is used to transfer ownership of UMEM frames from
user-space to kernel-space. The UMEM addrs are passed in the ring. As
an example, if the UMEM is 64k and each chunk is 4k, then the UMEM has
16 chunks and can pass addrs between 0 and 64k.
Frames passed to the kernel are used for the ingress path (RX rings).
The user application produces UMEM addrs to this ring. Note that, if
running the application with aligned chunk mode, the kernel will mask
the incoming addr. E.g. for a chunk size of 2k, the log2(2048) LSB of
the addr will be masked off, meaning that 2048, 2050 and 3000 refers
to the same chunk. If the user application is run in the unaligned
chunks mode, then the incoming addr will be left untouched.
UMEM COMPLETION ring
169-181UMEM COMPLETION ring
COMPLETION ring은 UMEM frame의 소유권을 kernel 공간에서 사용자 공간으로 넘깁니다. FILL ring과 마찬가지로 UMEM index를 사용합니다.
Kernel이 사용자 공간으로 넘기는 frame은 TX ring을 통해 송신을 끝내 사용자 공간이 다시 사용할 수 있는 frame입니다.
사용자 애플리케이션이 이 ring에서 UMEM `addr`를 소비합니다.
UMEM Completion Ring
~~~~~~~~~~~~~~~~~~~~
The COMPLETION Ring is used transfer ownership of UMEM frames from
kernel-space to user-space. Just like the FILL ring, UMEM indices are
used.
Frames passed from the kernel to user-space are frames that has been
sent (TX ring) and can be used by user-space again.
The user application consumes UMEM addrs from this ring.
RX ring
182-194RX ring
RX ring은 socket의 수신 측입니다. 각 entry는 `struct xdp_desc` descriptor이며 UMEM offset인 `addr`와 data 길이인 `len`을 담습니다.
FILL ring을 통해 kernel에 frame을 넘기지 않았다면 RX ring에는 descriptor가 나타나지 않으며 나타날 수도 없습니다.
사용자 애플리케이션이 이 ring에서 `struct xdp_desc` descriptor를 소비합니다.
RX Ring
~~~~~~~
The RX ring is the receiving side of a socket. Each entry in the ring
is a struct xdp_desc descriptor. The descriptor contains UMEM offset
(addr) and the length of the data (len).
If no frames have been passed to kernel via the FILL ring, no
descriptors will (or can) appear on the RX ring.
The user application consumes struct xdp_desc descriptors from this
ring.
TX ring
195-206TX ring
TX ring은 frame을 보내는 데 사용합니다. `struct xdp_desc` descriptor에 index, length, offset을 채워 ring에 넣습니다.
전송을 시작하려면 `sendmsg()` system call이 필요합니다. 향후 이 요구는 완화될 수 있습니다.
사용자 애플리케이션이 이 ring에 `struct xdp_desc` descriptor를 생산합니다.
TX Ring
~~~~~~~
The TX ring is used to send frames. The struct xdp_desc descriptor is
filled (index, length and offset) and passed into the ring.
To start the transfer a sendmsg() system call is required. This might
be relaxed in the future.
The user application produces struct xdp_desc descriptors to this
ring.
libbpf
207-219libbpf
libbpf는 eBPF와 XDP를 더 쉽게 사용할 수 있게 하는 helper library입니다. AF_XDP 사용을 돕는 전용 helper 함수도 `tools/testing/selftests/bpf/xsk.h`에 들어 있습니다. 함수는 AF_XDP socket 설정을 쉽게 만드는 control-plane 함수와 ring을 안전하고 빠르게 접근하는 data-plane 함수 두 종류로 나뉩니다.
숙련된 power user가 아니라면 이 library를 사용하기를 권장합니다. 프로그램이 훨씬 단순해집니다.
Libbpf
======
Libbpf is a helper library for eBPF and XDP that makes using these
technologies a lot simpler. It also contains specific helper functions
in tools/testing/selftests/bpf/xsk.h for facilitating the use of
AF_XDP. It contains two types of functions: those that can be used to
make the setup of AF_XDP socket easier and ones that can be used in the
data plane to access the rings safely and quickly.
We recommend that you use this library unless you have become a power
user. It will make your program a lot simpler.
XSKMAP
220-236XSKMAP / `BPF_MAP_TYPE_XSKMAP`
XDP 측에는 `bpf_redirect_map()`과 함께 사용해 ingress frame을 socket으로 전달하는 BPF map 형식 `BPF_MAP_TYPE_XSKMAP`, 즉 XSKMAP이 있습니다.
사용자 애플리케이션은 `bpf()` system call로 socket을 map에 넣습니다.
XDP 프로그램이 queue 구성과 netdev가 일치하지 않는 socket으로 redirect하려 하면 frame을 drop합니다. 예를 들어 AF_XDP socket이 netdev `eth0`의 queue 17에 bind되었다면 `eth0` queue 17에서 실행되는 XDP 프로그램만 그 socket으로 data를 성공적으로 보낼 수 있습니다. 예는 `samples/bpf/`의 sample application을 참조하십시오.
XSKMAP / BPF_MAP_TYPE_XSKMAP
============================
On XDP side there is a BPF map type BPF_MAP_TYPE_XSKMAP (XSKMAP) that
is used in conjunction with bpf_redirect_map() to pass the ingress
frame to a socket.
The user application inserts the socket into the map, via the bpf()
system call.
Note that if an XDP program tries to redirect to a socket that does
not match the queue configuration and netdev, the frame will be
dropped. E.g. an AF_XDP socket is bound to netdev eth0 and
queue 17. Only the XDP program executing for eth0 and queue 17 will
successfully pass data to the socket. Please refer to the sample
application (samples/bpf/) in for an example.
구성 flag와 copy mode
237-254구성 flag와 socket option
다음 flag들은 AF_XDP socket 동작을 제어하고 감시하는 데 사용합니다.
`XDP_COPY`와 `XDP_ZEROCOPY` bind flag
Socket을 bind하면 kernel은 먼저 zero-copy를 시도하고, 지원하지 않으면 모든 패킷을 사용자 공간으로 복사하는 copy mode로 fallback합니다. 특정 mode를 강제하려면 flag를 사용합니다. `bind`에 `XDP_COPY`를 전달하면 copy mode를 강제하고 사용할 수 없으면 오류로 실패합니다. 반대로 `XDP_ZEROCOPY`는 zero-copy mode를 강제하고 사용할 수 없으면 실패합니다.
Configuration Flags and Socket Options
======================================
These are the various configuration flags that can be used to control
and monitor the behavior of AF_XDP sockets.
XDP_COPY and XDP_ZEROCOPY bind flags
------------------------------------
When you bind to a socket, the kernel will first try to use zero-copy
copy. If zero-copy is not supported, it will fall back on using copy
mode, i.e. copying all packets out to user space. But if you would
like to force a certain mode, you can use the following flags. If you
pass the XDP_COPY flag to the bind call, the kernel will force the
socket into copy mode. If it cannot use copy mode, the bind call will
fail with an error. Conversely, the XDP_ZEROCOPY flag will force the
socket into zero-copy mode or fail.
XDP_USE_NEED_WAKEUP
347-387`XDP_USE_NEED_WAKEUP` bind flag
이 option은 사용자 공간이 producer인 FILL ring과 TX ring에 `need_wakeup` flag를 추가합니다. `bind`에서 option을 설정했을 때 kernel이 패킷 처리를 계속하려면 system call로 명시적으로 깨워야 하는 경우 flag가 설정됩니다. Flag가 0이면 system call이 필요하지 않습니다.
FILL ring의 flag가 설정되면 애플리케이션은 RX ring에서 계속 패킷을 받기 위해 `poll()`을 호출해야 합니다. 예를 들어 kernel이 FILL ring에 buffer가 없고 NIC RX hardware ring에도 buffer가 남지 않았음을 감지하면 NIC가 패킷을 받을 수 없으므로 interrupt를 끄고 `need_wakeup`을 설정합니다. 사용자 공간은 FILL ring에 buffer를 넣고 `poll()`을 호출해 kernel driver가 이를 hardware ring에 올리고 수신을 재개하게 합니다.
TX ring의 flag가 설정되면 애플리케이션이 TX ring에 넣은 패킷을 보내도록 kernel에 명시적으로 알려야 합니다. RX 경로처럼 `poll()`을 호출하거나 `sendto()`를 호출하면 됩니다.
libbpf helper를 사용하는 TX 경로 예는 다음과 같습니다.
.. code-block:: c
if (xsk_ring_prod__needs_wakeup(&my_tx_ring))
sendto(xsk_socket__fd(xsk_handle), NULL, 0, MSG_DONTWAIT, NULL, 0);
즉, flag가 설정된 경우에만 system call을 사용합니다.
보통 더 높은 성능을 내므로 이 mode를 항상 켜기를 권장합니다. 애플리케이션과 driver가 같은 core에서 실행될 때 특히 유리하고, 서로 다른 core를 사용하더라도 TX 경로의 system call 수를 줄입니다.
XDP_USE_NEED_WAKEUP bind flag
-----------------------------
This option adds support for a new flag called need_wakeup that is
present in the FILL ring and the TX ring, the rings for which user
space is a producer. When this option is set in the bind call, the
need_wakeup flag will be set if the kernel needs to be explicitly
woken up by a syscall to continue processing packets. If the flag is
zero, no syscall is needed.
If the flag is set on the FILL ring, the application needs to call
poll() to be able to continue to receive packets on the RX ring. This
can happen, for example, when the kernel has detected that there are no
more buffers on the FILL ring and no buffers left on the RX HW ring of
the NIC. In this case, interrupts are turned off as the NIC cannot
receive any packets (as there are no buffers to put them in), and the
need_wakeup flag is set so that user space can put buffers on the
FILL ring and then call poll() so that the kernel driver can put these
buffers on the HW ring and start to receive packets.
If the flag is set for the TX ring, it means that the application
needs to explicitly notify the kernel to send any packets put on the
TX ring. This can be accomplished either by a poll() call, as in the
RX path, or by calling sendto().
An example with the use of libbpf helpers would look like this for the
TX path:
.. code-block:: c
if (xsk_ring_prod__needs_wakeup(&my_tx_ring))
sendto(xsk_socket__fd(xsk_handle), NULL, 0, MSG_DONTWAIT, NULL, 0);
I.e., only use the syscall if the flag is set.
We recommend that you always enable this mode as it usually leads to
better performance especially if you run the application and the
driver on the same core, but also if you use different cores for the
application and the kernel driver, as it reduces the number of
syscalls needed for the TX path.
Ring setsockopt
388-413`XDP_{RX|TX|UMEM_FILL|UMEM_COMPLETION}_RING` setsockopt
이 setsockopt들은 각각 RX, TX, FILL, COMPLETION ring의 descriptor 수를 정합니다. RX와 TX ring 중 적어도 하나의 크기는 반드시 설정해야 합니다. 둘 다 설정하면 애플리케이션에서 수신과 송신을 모두 할 수 있고, 한 방향만 필요하면 해당 ring만 만들어 자원을 절약할 수 있습니다.
Socket에는 UMEM이 연결되어야 하므로 FILL과 COMPLETION ring은 둘 다 필수입니다. 단, `XDP_SHARED_UMEM`을 사용하면 첫 socket 이후의 socket은 자체 UMEM이 없으므로 공유 UMEM의 ring을 사용하며 FILL·COMPLETION ring을 만들면 안 됩니다. Ring은 single-producer/single-consumer이므로 여러 process가 동시에 접근하면 안 됩니다.
libbpf에서는 `xsk_socket__create` 함수의 RX 또는 TX argument에 각각 `NULL`을 넘겨 RX-only 또는 TX-only socket을 만들 수 있습니다.
TX-only socket을 만들었다면 FILL ring에 패킷을 넣지 않기를 권장합니다. 넣으면 driver가 실제로는 수신하지 않을 애플리케이션이 수신할 것이라고 판단해 성능에 악영향을 줄 수 있습니다.
XDP_{RX|TX|UMEM_FILL|UMEM_COMPLETION}_RING setsockopts
------------------------------------------------------
These setsockopts sets the number of descriptors that the RX, TX,
FILL, and COMPLETION rings respectively should have. It is mandatory
to set the size of at least one of the RX and TX rings. If you set
both, you will be able to both receive and send traffic from your
application, but if you only want to do one of them, you can save
resources by only setting up one of them. Both the FILL ring and the
COMPLETION ring are mandatory as you need to have a UMEM tied to your
socket. But if the XDP_SHARED_UMEM flag is used, any socket after the
first one does not have a UMEM and should in that case not have any
FILL or COMPLETION rings created as the ones from the shared UMEM will
be used. Note, that the rings are single-producer single-consumer, so
do not try to access them from multiple processes at the same
time. See the XDP_SHARED_UMEM section.
In libbpf, you can create Rx-only and Tx-only sockets by supplying
NULL to the rx and tx arguments, respectively, to the
xsk_socket__create function.
If you create a Tx-only socket, we recommend that you do not put any
packets on the fill ring. If you do this, drivers might think you are
going to receive something when you in fact will not, and this can
negatively impact performance.
XDP_UMEM_REG
414-431`XDP_UMEM_REG` setsockopt
이 setsockopt는 UMEM을 socket에 등록합니다. UMEM은 패킷이 머물 수 있는 모든 buffer를 담는 영역입니다. 호출에는 영역 시작 pointer와 전체 크기를 전달합니다. `chunk_size` parameter는 UMEM을 나누는 단위 크기이며 현재는 2 KiB 또는 4 KiB만 가능합니다. UMEM이 128 KiB이고 chunk가 2 KiB라면 최대 64개 패킷을 담을 수 있고 가장 큰 패킷 크기는 2 KiB입니다.
UMEM의 각 buffer에 headroom을 설정할 수도 있습니다. N byte로 설정하면 패킷은 buffer 시작에서 N byte 뒤에 놓이고 앞의 N byte는 애플리케이션이 사용할 수 있습니다. 마지막 option은 `flags` field이며 각 UMEM flag 절에서 따로 다룹니다.
XDP_UMEM_REG setsockopt
-----------------------
This setsockopt registers a UMEM to a socket. This is the area that
contain all the buffers that packet can reside in. The call takes a
pointer to the beginning of this area and the size of it. Moreover, it
also has parameter called chunk_size that is the size that the UMEM is
divided into. It can only be 2K or 4K at the moment. If you have an
UMEM area that is 128K and a chunk size of 2K, this means that you
will be able to hold a maximum of 128K / 2K = 64 packets in your UMEM
area and that your largest packet size can be 2K.
There is also an option to set the headroom of each single buffer in
the UMEM. If you set this to N bytes, it means that the packet will
start N bytes into the buffer leaving the first N bytes for the
application to use. The final option is the flags field, but it will
be dealt with in separate sections for each UMEM flag.
SO_BINDTODEVICE
432-440`SO_BINDTODEVICE` setsockopt
특정 network interface에 AF_XDP socket을 고정하는 범용 `SOL_SOCKET` option입니다. Privileged process가 socket을 만든 뒤 non-privileged process에 넘길 때 유용합니다. Option을 설정하면 kernel은 그 socket을 다른 interface에 bind하려는 시도를 거부합니다. 값을 갱신하려면 `CAP_NET_RAW`가 필요합니다.
SO_BINDTODEVICE setsockopt
--------------------------
This is a generic SOL_SOCKET option that can be used to tie AF_XDP
socket to a particular network interface. It is useful when a socket
is created by a privileged process and passed to a non-privileged one.
Once the option is set, kernel will refuse attempts to bind that socket
to a different interface. Updating the value requires CAP_NET_RAW.
XDP_MAX_TX_SKB_BUDGET
441-449`XDP_MAX_TX_SKB_BUDGET` setsockopt
한 번의 send system call에서 처리해 driver에 넘길 수 있는 최대 descriptor 수를 정합니다. Copy mode에서 애플리케이션이 socket별 최대 반복 횟수를 조정해 throughput을 높이고 send system call 빈도를 낮출 수 있게 합니다. 허용 범위는 `[32, xs->tx->nentries]`입니다.
XDP_MAX_TX_SKB_BUDGET setsockopt
--------------------------------
This setsockopt sets the maximum number of descriptors that can be handled
and passed to the driver at one send syscall. It is applied in the copy
mode to allow application to tune the per-socket maximum iteration for
better throughput and less frequency of send syscall.
Allowed range is [32, xs->tx->nentries].
XDP_STATISTICS
450-463`XDP_STATISTICS` getsockopt
Debug에 유용한 socket drop 통계를 가져옵니다. 지원하는 통계는 다음과 같습니다.
.. code-block:: c
struct xdp_statistics {
__u64 rx_dropped; /* Dropped for reasons other than invalid desc */
__u64 rx_invalid_descs; /* Dropped due to invalid descriptor */
__u64 tx_invalid_descs; /* Dropped due to invalid descriptor */
};
XDP_STATISTICS getsockopt
-------------------------
Gets drop statistics of a socket that can be useful for debug
purposes. The supported statistics are shown below:
.. code-block:: c
struct xdp_statistics {
__u64 rx_dropped; /* Dropped for reasons other than invalid desc */
__u64 rx_invalid_descs; /* Dropped due to invalid descriptor */
__u64 tx_invalid_descs; /* Dropped due to invalid descriptor */
};
XDP_OPTIONS
464-469`XDP_OPTIONS` getsockopt
XDP socket option을 가져옵니다. 현재 지원하는 것은 zero-copy가 켜졌는지 알려 주는 `XDP_OPTIONS_ZEROCOPY`뿐입니다.
XDP_OPTIONS getsockopt
----------------------
Gets options from an XDP socket. The only one supported so far is
XDP_OPTIONS_ZEROCOPY which tells you if zero-copy is on or not.
Multi-buffer 지원
470-553Multi-buffer 지원
Multi-buffer 지원을 사용하면 AF_XDP socket 프로그램이 copy mode와 zero-copy mode 모두에서 여러 buffer로 이루어진 패킷을 송수신할 수 있습니다. 예를 들어 header가 든 frame과 data가 든 frame 두 개로 패킷을 만들거나, 4 KiB frame 세 개를 이어 9 KiB Ethernet jumbo frame을 만들 수 있습니다.
정의는 다음과 같습니다.
- 패킷은 하나 이상의 frame으로 이루어집니다.
- AF_XDP ring의 descriptor 하나는 언제나 frame 하나를 가리킵니다. 패킷이 frame 하나로 이루어졌다면 descriptor가 전체 패킷을 가리킵니다.
AF_XDP socket에서 multi-buffer 지원을 켜려면 새 bind flag `XDP_USE_SG`를 사용합니다. 제공하지 않으면 이전처럼 모든 multi-buffer 패킷을 drop합니다. Load된 XDP 프로그램도 multi-buffer mode여야 하며, XDP 프로그램 section 이름으로 `xdp.frags`를 사용하면 됩니다.
여러 frame으로 이루어진 패킷을 표현하기 위해 RX·TX descriptor의 `options` field에 `XDP_PKT_CONTD` flag가 도입되었습니다. 값이 1이면 패킷이 다음 descriptor에서 계속되고, 0이면 현재 descriptor가 마지막입니다. 많은 NIC의 end-of-packet(EOP) flag와 반대인 이유는 기존 non-multi-buffer 애플리케이션과 호환하기 위해서입니다. 기존 RX 애플리케이션은 이 bit가 모든 패킷에서 0이고 TX 애플리케이션은 `options`를 0으로 설정하며, 다른 값은 invalid descriptor로 처리되기 때문입니다.
여러 frame 패킷을 AF_XDP TX ring에 생산할 때의 의미는 다음과 같습니다.
- Invalid descriptor를 만나면 같은 패킷의 다른 descriptor·frame도 모두 invalid로 표시하고 complete하지 않습니다. 다음 descriptor는 의도와 관계없이 새 패킷의 시작으로 취급합니다. 의도를 추측할 수 없기 때문입니다. Invalid descriptor를 생산한다면 프로그램 버그이므로 수정해야 합니다.
- 길이가 0인 descriptor는 invalid descriptor로 처리합니다.
- Copy mode에서 패킷 하나가 가질 수 있는 최대 frame 수는 `CONFIG_MAX_SKB_FRAGS + 1`입니다. 이를 넘으면 지금까지 모은 descriptor를 모두 drop하고 invalid로 처리합니다. 어떤 system에서도 동작하게 하려면 config 최소값이 17이므로 fragment 수를 18로 제한하십시오.
- Zero-copy mode의 한계는 NIC hardware가 지원하는 수까지입니다. 확인한 NIC들은 보통 적어도 5개를 지원합니다. NIC 한계에 맞추려고 내부에서 copy를 발생시키면 zero-copy 목적을 훼손하므로 `CONFIG_MAX_SKB_FRAGS + 1` 같은 고정 한계를 일부러 강제하지 않습니다. 한계 조사 방법은 multi-buffer 지원 probe 절에서 설명합니다.
Copy-mode RX 경로에서 xsk core는 필요하면 XDP data를 여러 descriptor로 복사하고 `XDP_PKT_CONTD`를 설정합니다. Zero-copy mode도 data를 복사하지 않는다는 점을 제외하면 같습니다. 애플리케이션이 `XDP_PKT_CONTD == 1`인 descriptor를 받으면 패킷이 여러 buffer로 이루어져 다음 descriptor에서 이어짐을 뜻합니다. `XDP_PKT_CONTD == 0`이면 패킷의 마지막 buffer입니다.
AF_XDP는 완전한 패킷, 즉 그 패킷의 모든 frame만 애플리케이션에 보냅니다. AF_XDP RX ring에 공간이 부족하면 패킷의 모든 frame을 drop합니다.
애플리케이션이 libxdp interface 등으로 descriptor batch를 읽을 때 batch가 완전한 패킷에서 끝난다는 보장은 없습니다. Libxdp interface는 ring 전체를 읽지 않으므로 batch가 패킷 중간에서 끝나고 나머지 buffer가 다음 batch의 처음에 올 수 있습니다. Batch 크기가 매우 크거나 ring이 매우 작은 경우는 예외입니다.
RX와 TX multi-buffer 지원 예제 프로그램은 이 문서 뒤쪽에 각각 나옵니다.
Multi-Buffer Support
====================
With multi-buffer support, programs using AF_XDP sockets can receive
and transmit packets consisting of multiple buffers both in copy and
zero-copy mode. For example, a packet can consist of two
frames/buffers, one with the header and the other one with the data,
or a 9K Ethernet jumbo frame can be constructed by chaining together
three 4K frames.
Some definitions:
* A packet consists of one or more frames
* A descriptor in one of the AF_XDP rings always refers to a single
frame. In the case the packet consists of a single frame, the
descriptor refers to the whole packet.
To enable multi-buffer support for an AF_XDP socket, use the new bind
flag XDP_USE_SG. If this is not provided, all multi-buffer packets
will be dropped just as before. Note that the XDP program loaded also
needs to be in multi-buffer mode. This can be accomplished by using
"xdp.frags" as the section name of the XDP program used.
To represent a packet consisting of multiple frames, a new flag called
XDP_PKT_CONTD is introduced in the options field of the Rx and Tx
descriptors. If it is true (1) the packet continues with the next
descriptor and if it is false (0) it means this is the last descriptor
of the packet. Why the reverse logic of end-of-packet (eop) flag found
in many NICs? Just to preserve compatibility with non-multi-buffer
applications that have this bit set to false for all packets on Rx,
and the apps set the options field to zero for Tx, as anything else
will be treated as an invalid descriptor.
These are the semantics for producing packets onto AF_XDP Tx ring
consisting of multiple frames:
* When an invalid descriptor is found, all the other
descriptors/frames of this packet are marked as invalid and not
completed. The next descriptor is treated as the start of a new
packet, even if this was not the intent (because we cannot guess
the intent). As before, if your program is producing invalid
descriptors you have a bug that must be fixed.
* Zero length descriptors are treated as invalid descriptors.
* For copy mode, the maximum supported number of frames in a packet is
equal to CONFIG_MAX_SKB_FRAGS + 1. If it is exceeded, all
descriptors accumulated so far are dropped and treated as
invalid. To produce an application that will work on any system
regardless of this config setting, limit the number of frags to 18,
as the minimum value of the config is 17.
* For zero-copy mode, the limit is up to what the NIC HW
supports. Usually at least five on the NICs we have checked. We
consciously chose to not enforce a rigid limit (such as
CONFIG_MAX_SKB_FRAGS + 1) for zero-copy mode, as it would have
resulted in copy actions under the hood to fit into what limit the
NIC supports. Kind of defeats the purpose of zero-copy mode. How to
probe for this limit is explained in the "probe for multi-buffer
support" section.
On the Rx path in copy-mode, the xsk core copies the XDP data into
multiple descriptors, if needed, and sets the XDP_PKT_CONTD flag as
detailed before. Zero-copy mode works the same, though the data is not
copied. When the application gets a descriptor with the XDP_PKT_CONTD
flag set to one, it means that the packet consists of multiple buffers
and it continues with the next buffer in the following
descriptor. When a descriptor with XDP_PKT_CONTD == 0 is received, it
means that this is the last buffer of the packet. AF_XDP guarantees
that only a complete packet (all frames in the packet) is sent to the
application. If there is not enough space in the AF_XDP Rx ring, all
frames of the packet will be dropped.
If application reads a batch of descriptors, using for example the libxdp
interfaces, it is not guaranteed that the batch will end with a full
packet. It might end in the middle of a packet and the rest of the
buffers of that packet will arrive at the beginning of the next batch,
since the libxdp interface does not read the whole ring (unless you
have an enormous batch size or a very small ring size).
An example program each for Rx and Tx multi-buffer support can be found
later in this document.
기본 사용법
554-632사용법
AF_XDP socket을 사용하려면 사용자 공간 애플리케이션과 XDP 프로그램 두 부분이 필요합니다. 전체 설정·사용 예는 `https://github.com/xdp-project/bpf-examples/tree/main/AF_XDP-example`의 xdp-project를 참조하십시오.
다음 XDP code는 현재 RX queue index에 해당하는 XSKMAP entry가 있으면 그 socket으로 redirect하고, 없으면 `XDP_PASS`를 반환합니다.
.. code-block:: c
SEC("xdp_sock") int xdp_sock_prog(struct xdp_md *ctx)
{
int index = ctx->rx_queue_index;
// A set entry here means that the corresponding queue_id
// has an active AF_XDP socket bound to it.
if (bpf_map_lookup_elem(&xsks_map, &index))
return bpf_redirect_map(&xsks_map, index, 0);
return XDP_PASS;
}
A simple but not so performance ring dequeue and enqueue could look
다음은 단순하지만 성능은 높지 않은 ring dequeue·enqueue 예입니다. Producer와 consumer 차이로 사용 entry와 빈 entry 수를 계산하고, ring 크기가 2의 거듭제곱이라는 점을 이용해 `index & (RING_SIZE - 1)`로 descriptor 위치를 구합니다. 실제 구현에서는 표시된 read·write barrier가 필요합니다.
.. code-block:: c
// struct xdp_rxtx_ring {
// __u32 *producer;
// __u32 *consumer;
// struct xdp_desc *desc;
// };
// struct xdp_umem_ring {
// __u32 *producer;
// __u32 *consumer;
// __u64 *desc;
// };
// typedef struct xdp_rxtx_ring RING;
// typedef struct xdp_umem_ring RING;
// typedef struct xdp_desc RING_TYPE;
// typedef __u64 RING_TYPE;
int dequeue_one(RING *ring, RING_TYPE *item)
{
__u32 entries = *ring->producer - *ring->consumer;
if (entries == 0)
return -1;
// read-barrier!
*item = ring->desc[*ring->consumer & (RING_SIZE - 1)];
(*ring->consumer)++;
return 0;
}
int enqueue_one(RING *ring, const RING_TYPE *item)
{
u32 free_entries = RING_SIZE - (*ring->producer - *ring->consumer);
if (free_entries == 0)
return -1;
ring->desc[*ring->producer & (RING_SIZE - 1)] = *item;
// write-barrier!
(*ring->producer)++;
return 0;
}
최적화되어 있고 바로 사용할 수 있는 libbpf 함수를 사용하십시오. 프로그램 작성이 더 쉬워집니다.
Usage
-----
In order to use AF_XDP sockets two parts are needed. The user-space
application and the XDP program. For a complete setup and usage example,
please refer to the xdp-project at
https://github.com/xdp-project/bpf-examples/tree/main/AF_XDP-example.
The XDP code sample is the following:
.. code-block:: c
SEC("xdp_sock") int xdp_sock_prog(struct xdp_md *ctx)
{
int index = ctx->rx_queue_index;
// A set entry here means that the corresponding queue_id
// has an active AF_XDP socket bound to it.
if (bpf_map_lookup_elem(&xsks_map, &index))
return bpf_redirect_map(&xsks_map, index, 0);
return XDP_PASS;
}
A simple but not so performance ring dequeue and enqueue could look
like this:
.. code-block:: c
// struct xdp_rxtx_ring {
// __u32 *producer;
// __u32 *consumer;
// struct xdp_desc *desc;
// };
// struct xdp_umem_ring {
// __u32 *producer;
// __u32 *consumer;
// __u64 *desc;
// };
// typedef struct xdp_rxtx_ring RING;
// typedef struct xdp_umem_ring RING;
// typedef struct xdp_desc RING_TYPE;
// typedef __u64 RING_TYPE;
int dequeue_one(RING *ring, RING_TYPE *item)
{
__u32 entries = *ring->producer - *ring->consumer;
if (entries == 0)
return -1;
// read-barrier!
*item = ring->desc[*ring->consumer & (RING_SIZE - 1)];
(*ring->consumer)++;
return 0;
}
int enqueue_one(RING *ring, const RING_TYPE *item)
{
u32 free_entries = RING_SIZE - (*ring->producer - *ring->consumer);
if (free_entries == 0)
return -1;
ring->desc[*ring->producer & (RING_SIZE - 1)] = *item;
// write-barrier!
(*ring->producer)++;
return 0;
}
But please use the libbpf functions as they are optimized and ready to
use. Will make your life easier.
Multi-buffer RX 사용법
633-672Multi-buffer RX 사용법
다음은 간결함을 위해 오류 경로를 제외하고 libxdp interface를 사용한 단순 RX 경로 pseudo-code입니다.
.. code-block:: c
void rx_packets(struct xsk_socket_info *xsk)
{
static bool new_packet = true;
u32 idx_rx = 0, idx_fq = 0;
static char *pkt;
int rcvd = xsk_ring_cons__peek(&xsk->rx, opt_batch_size, &idx_rx);
xsk_ring_prod__reserve(&xsk->umem->fq, rcvd, &idx_fq);
for (int i = 0; i < rcvd; i++) {
struct xdp_desc *desc = xsk_ring_cons__rx_desc(&xsk->rx, idx_rx++);
char *frag = xsk_umem__get_data(xsk->umem->buffer, desc->addr);
bool eop = !(desc->options & XDP_PKT_CONTD);
if (new_packet)
pkt = frag;
else
add_frag_to_pkt(pkt, frag);
if (eop)
process_pkt(pkt);
new_packet = eop;
*xsk_ring_prod__fill_addr(&xsk->umem->fq, idx_fq++) = desc->addr;
}
xsk_ring_prod__submit(&xsk->umem->fq, rcvd);
xsk_ring_cons__release(&xsk->rx, rcvd);
예제는 RX descriptor를 순회하며 `XDP_PKT_CONTD`의 반대값으로 EOP를 판단합니다. 새 패킷이면 첫 fragment를 저장하고, 이어지는 fragment는 기존 패킷에 붙입니다. EOP에서 완성 패킷을 처리한 뒤 각 frame `addr`를 FILL ring에 되돌리고 RX descriptor를 release합니다.
Usage Multi-Buffer Rx
---------------------
Here is a simple Rx path pseudo-code example (using libxdp interfaces
for simplicity). Error paths have been excluded to keep it short:
.. code-block:: c
void rx_packets(struct xsk_socket_info *xsk)
{
static bool new_packet = true;
u32 idx_rx = 0, idx_fq = 0;
static char *pkt;
int rcvd = xsk_ring_cons__peek(&xsk->rx, opt_batch_size, &idx_rx);
xsk_ring_prod__reserve(&xsk->umem->fq, rcvd, &idx_fq);
for (int i = 0; i < rcvd; i++) {
struct xdp_desc *desc = xsk_ring_cons__rx_desc(&xsk->rx, idx_rx++);
char *frag = xsk_umem__get_data(xsk->umem->buffer, desc->addr);
bool eop = !(desc->options & XDP_PKT_CONTD);
if (new_packet)
pkt = frag;
else
add_frag_to_pkt(pkt, frag);
if (eop)
process_pkt(pkt);
new_packet = eop;
*xsk_ring_prod__fill_addr(&xsk->umem->fq, idx_fq++) = desc->addr;
}
xsk_ring_prod__submit(&xsk->umem->fq, rcvd);
xsk_ring_cons__release(&xsk->rx, rcvd);
}
Multi-buffer TX 사용법
673-722Multi-buffer TX 사용법
다음은 단순화를 위해 UMEM 크기가 유한해 결국 송신할 패킷이 고갈된다는 점을 무시하고, `pkts.addr`가 UMEM의 유효한 위치를 가리킨다고 가정한 libxdp TX 경로 pseudo-code입니다.
.. code-block:: c
void tx_packets(struct xsk_socket_info *xsk, struct pkt *pkts,
int batch_size)
{
u32 idx, i, pkt_nb = 0;
xsk_ring_prod__reserve(&xsk->tx, batch_size, &idx);
for (i = 0; i < batch_size;) {
u64 addr = pkts[pkt_nb].addr;
u32 len = pkts[pkt_nb].size;
do {
struct xdp_desc *tx_desc;
tx_desc = xsk_ring_prod__tx_desc(&xsk->tx, idx + i++);
tx_desc->addr = addr;
if (len > xsk_frame_size) {
tx_desc->len = xsk_frame_size;
tx_desc->options = XDP_PKT_CONTD;
} else {
tx_desc->len = len;
tx_desc->options = 0;
pkt_nb++;
}
len -= tx_desc->len;
addr += xsk_frame_size;
if (i == batch_size) {
/* Remember len, addr, pkt_nb for next iteration.
* Skipped for simplicity.
*/
break;
}
} while (len);
}
xsk_ring_prod__submit(&xsk->tx, i);
패킷 길이가 frame 크기보다 크면 현재 descriptor 길이를 frame 크기로 정하고 `XDP_PKT_CONTD`를 설정합니다. 마지막 frame에서는 남은 길이를 쓰고 `options = 0`으로 끝을 표시합니다. 예약한 batch가 패킷 중간에서 끝나면 다음 반복을 위해 `len`, `addr`, `pkt_nb`를 기억해야 하지만 예제에서는 생략합니다.
Usage Multi-Buffer Tx
---------------------
Here is an example Tx path pseudo-code (using libxdp interfaces for
simplicity) ignoring that the umem is finite in size, and that we
eventually will run out of packets to send. Also assumes pkts.addr
points to a valid location in the umem.
.. code-block:: c
void tx_packets(struct xsk_socket_info *xsk, struct pkt *pkts,
int batch_size)
{
u32 idx, i, pkt_nb = 0;
xsk_ring_prod__reserve(&xsk->tx, batch_size, &idx);
for (i = 0; i < batch_size;) {
u64 addr = pkts[pkt_nb].addr;
u32 len = pkts[pkt_nb].size;
do {
struct xdp_desc *tx_desc;
tx_desc = xsk_ring_prod__tx_desc(&xsk->tx, idx + i++);
tx_desc->addr = addr;
if (len > xsk_frame_size) {
tx_desc->len = xsk_frame_size;
tx_desc->options = XDP_PKT_CONTD;
} else {
tx_desc->len = len;
tx_desc->options = 0;
pkt_nb++;
}
len -= tx_desc->len;
addr += xsk_frame_size;
if (i == batch_size) {
/* Remember len, addr, pkt_nb for next iteration.
* Skipped for simplicity.
*/
break;
}
} while (len);
}
xsk_ring_prod__submit(&xsk->tx, i);
}
Multi-buffer 지원 조사
723-749Multi-buffer 지원 조사
Driver가 SKB 또는 DRV mode에서 multi-buffer AF_XDP를 지원하는지 알아보려면 `linux/netdev.h`의 netlink `XDP_FEATURES` feature를 사용해 `NETDEV_XDP_ACT_RX_SG` 지원을 질의합니다. XDP multi-buffer 지원을 질의할 때와 같은 flag입니다. Driver가 XDP multi-buffer를 지원하면 AF_XDP도 SKB·DRV mode에서 이를 지원합니다.
Zero-copy mode 지원 여부를 알아보려면 `XDP_FEATURES`에서 먼저 `NETDEV_XDP_ACT_XSK_ZEROCOPY` flag를 확인합니다. 설정되어 있으면 적어도 zero-copy를 지원하므로 `linux/netdev.h`의 netlink attribute `NETDEV_A_DEV_XDP_ZC_MAX_SEGS`를 확인합니다. 반환되는 unsigned integer는 이 device가 zero-copy mode에서 지원하는 최대 fragment 수입니다.
- `1`: 최대 한 fragment만 지원하므로 이 device는 zero-copy multi-buffer를 지원하지 않습니다.
- `>= 2`: 이 device는 zero-copy multi-buffer를 지원하며 반환값이 최대 fragment 수입니다.
libbpf를 통한 사용 예는 `tools/testing/selftests/bpf/xskxceiver.c`를 참조하십시오.
Probing for Multi-Buffer Support
--------------------------------
To discover if a driver supports multi-buffer AF_XDP in SKB or DRV
mode, use the XDP_FEATURES feature of netlink in linux/netdev.h to
query for NETDEV_XDP_ACT_RX_SG support. This is the same flag as for
querying for XDP multi-buffer support. If XDP supports multi-buffer in
a driver, then AF_XDP will also support that in SKB and DRV mode.
To discover if a driver supports multi-buffer AF_XDP in zero-copy
mode, use XDP_FEATURES and first check the NETDEV_XDP_ACT_XSK_ZEROCOPY
flag. If it is set, it means that at least zero-copy is supported and
you should go and check the netlink attribute
NETDEV_A_DEV_XDP_ZC_MAX_SEGS in linux/netdev.h. An unsigned integer
value will be returned stating the max number of frags that are
supported by this device in zero-copy mode. These are the possible
return values:
1: Multi-buffer for zero-copy is not supported by this device, as max
one fragment supported means that multi-buffer is not possible.
>=2: Multi-buffer is supported in zero-copy mode for this device. The
returned number signifies the max number of frags supported.
For an example on how these are used through libbpf, please take a
look at tools/testing/selftests/bpf/xskxceiver.c.
Zero-copy driver의 multi-buffer
750-757Zero-copy driver의 multi-buffer 지원
Zero-copy driver는 보통 RX·TX 처리에 batch API를 사용합니다. TX batch API는 batch 마지막이 완전한 패킷으로 끝나는 TX descriptor batch를 제공한다고 보장합니다. 이는 zero-copy driver에 multi-buffer 지원을 쉽게 추가하기 위한 것입니다.
Multi-Buffer Support for Zero-Copy Drivers
------------------------------------------
Zero-copy drivers usually use the batched APIs for Rx and Tx
processing. Note that the Tx batch API guarantees that it will provide
a batch of Tx descriptors that ends with full packet at the end. This
to facilitate extending a zero-copy driver with multi-buffer support.
Sample application
758-783Sample application
Private UMEM을 사용하는 AF_XDP socket 예를 보여 주는 `xdpsock` benchmark·test application은 `https://github.com/xdp-project/bpf-examples/tree/main/AF_XDP-example`에서 찾을 수 있습니다.
UDP port 4242 traffic을 AF_XDP를 켤 queue 16으로 보내려면 다음처럼 `ethtool`을 사용합니다.
for this::
ethtool -N p3p2 rx-flow-hash udp4 fn
ethtool -N p3p2 flow-type udp4 src-port 4242 dst-port 4242 \
action 16
XDP_DRV mode에서 RX drop benchmark는 다음처럼 실행합니다.
using::
samples/bpf/xdpsock -i p3p2 -q 16 -r -N
XDP_SKB mode에서는 `-N` 대신 `-S` switch를 사용합니다. 평소처럼 `-h`로 모든 option을 표시할 수 있습니다.
이 sample application은 AF_XDP 설정과 사용을 단순하게 만들기 위해 libbpf를 사용합니다. 더 고급 기능을 위해 AF_XDP raw UAPI가 실제로 어떻게 사용되는지 보려면 `tools/testing/selftests/bpf/xsk.[ch]`의 libbpf code를 참조하십시오.
Sample application
==================
There is a xdpsock benchmarking/test application that can be found at
https://github.com/xdp-project/bpf-examples/tree/main/AF_XDP-example
that demonstrates how to use AF_XDP sockets with private
UMEMs. Say that you would like your UDP traffic from port 4242 to end
up in queue 16, that we will enable AF_XDP on. Here, we use ethtool
for this::
ethtool -N p3p2 rx-flow-hash udp4 fn
ethtool -N p3p2 flow-type udp4 src-port 4242 dst-port 4242 \
action 16
Running the rxdrop benchmark in XDP_DRV mode can then be done
using::
samples/bpf/xdpsock -i p3p2 -q 16 -r -N
For XDP_SKB mode, use the switch "-S" instead of "-N" and all options
can be displayed with "-h", as usual.
This sample application uses libbpf to make the setup and usage of
AF_XDP simpler. If you want to know how the raw uapi of AF_XDP is
really used to make something more advanced, take a look at the libbpf
code in tools/testing/selftests/bpf/xsk.[ch].
FAQ
784-841FAQ
Q: Socket에 traffic이 보이지 않습니다. 무엇이 잘못되었습니까?
A: 물리 NIC의 netdev를 초기화할 때 Linux는 보통 core마다 RX·TX queue pair 하나를 할당합니다. 8-core system이라면 core마다 하나씩 queue id 0~7을 만듭니다. AF_XDP `bind` 또는 libbpf `xsk_socket__create` 호출에서 특정 queue id를 지정하며, socket에는 그 queue로 향하는 traffic만 들어옵니다. Queue 0에 bind하면 queue 1~7로 분배된 traffic은 받지 못합니다. 운이 좋으면 traffic이 보일 수 있지만 보통 bind하지 않은 queue로 갑니다.
원하는 traffic을 bind한 queue id로 보내는 방법은 여러 가지입니다. 모든 traffic을 보려면 netdev가 queue 0 하나만 갖도록 강제한 뒤 queue 0에 bind할 수 있습니다.
id 0, and then bind to queue 0. You can use ethtool to do this::
sudo ethtool -L <interface> combined 1
Traffic 일부만 보려면 `ethtool`로 NIC를 설정해 원하는 traffic을 bind할 단일 queue로 filter할 수 있습니다. 다음 예는 port 4242로 오가는 UDP traffic을 queue 2로 보냅니다.
UDP traffic to and from port 4242 are sent to queue 2::
sudo ethtool -N <interface> rx-flow-hash udp4 fn
sudo ethtool -N <interface> flow-type udp4 src-port 4242 dst-port \
4242 action 2
그 밖의 방법은 NIC가 제공하는 기능에 따라 달라집니다.
Q: Copy mode에서 XSKMAP으로 서로 다른 UMEM 사이의 switch를 구현할 수 있습니까?
A: 현재는 지원하지 않습니다. XSKMAP은 queue id X로 들어온 traffic을 같은 queue id X에 bind된 socket으로만 전환할 수 있습니다. XSKMAP에 X와 Y처럼 서로 다른 queue id에 bind된 socket이 들어 있어도 queue id Y로 들어온 traffic만 같은 Y에 bind된 socket으로 보낼 수 있습니다. Zero-copy mode에서는 NIC의 switch 또는 다른 분배 메커니즘으로 traffic을 올바른 queue id와 socket에 보내야 합니다.
Q: 패킷이 가끔 손상됩니다. 무엇이 잘못되었습니까?
A: UMEM의 같은 buffer를 동시에 둘 이상의 ring에 넣지 않도록 주의해야 합니다. 같은 buffer를 FILL ring과 TX ring에 동시에 넣으면 NIC가 buffer로 data를 받는 동시에 그 buffer를 송신할 수 있어 패킷이 손상됩니다. `XDP_SHARED_UMEM`으로 bind한 서로 다른 queue id 또는 netdev의 FILL ring에 같은 buffer를 동시에 넣어도 같은 문제가 생깁니다.
FAQ
=======
Q: I am not seeing any traffic on the socket. What am I doing wrong?
A: When a netdev of a physical NIC is initialized, Linux usually
allocates one RX and TX queue pair per core. So on a 8 core system,
queue ids 0 to 7 will be allocated, one per core. In the AF_XDP
bind call or the xsk_socket__create libbpf function call, you
specify a specific queue id to bind to and it is only the traffic
towards that queue you are going to get on you socket. So in the
example above, if you bind to queue 0, you are NOT going to get any
traffic that is distributed to queues 1 through 7. If you are
lucky, you will see the traffic, but usually it will end up on one
of the queues you have not bound to.
There are a number of ways to solve the problem of getting the
traffic you want to the queue id you bound to. If you want to see
all the traffic, you can force the netdev to only have 1 queue, queue
id 0, and then bind to queue 0. You can use ethtool to do this::
sudo ethtool -L <interface> combined 1
If you want to only see part of the traffic, you can program the
NIC through ethtool to filter out your traffic to a single queue id
that you can bind your XDP socket to. Here is one example in which
UDP traffic to and from port 4242 are sent to queue 2::
sudo ethtool -N <interface> rx-flow-hash udp4 fn
sudo ethtool -N <interface> flow-type udp4 src-port 4242 dst-port \
4242 action 2
A number of other ways are possible all up to the capabilities of
the NIC you have.
Q: Can I use the XSKMAP to implement a switch between different umems
in copy mode?
A: The short answer is no, that is not supported at the moment. The
XSKMAP can only be used to switch traffic coming in on queue id X
to sockets bound to the same queue id X. The XSKMAP can contain
sockets bound to different queue ids, for example X and Y, but only
traffic goming in from queue id Y can be directed to sockets bound
to the same queue id Y. In zero-copy mode, you should use the
switch, or other distribution mechanism, in your NIC to direct
traffic to the correct queue id and socket.
Q: My packets are sometimes corrupted. What is wrong?
A: Care has to be taken not to feed the same buffer in the UMEM into
more than one ring at the same time. If you for example feed the
same buffer into the FILL ring and the TX ring at the same time, the
NIC might receive data into the buffer at the same time it is
sending it. This will cause some packets to become corrupted. Same
thing goes for feeding the same buffer into the FILL rings
belonging to different queue ids or netdevs bound with the
XDP_SHARED_UMEM flag.
기여자
842-855기여자
- Björn Töpel (AF_XDP core)
- Magnus Karlsson (AF_XDP core)
- Alexander Duyck
- Alexei Starovoitov
- Daniel Borkmann
- Jesper Dangaard Brouer
- John Fastabend
- Jonathan Corbet (LWN coverage)
- Michael S. Tsirkin
- Qi Z Zhang
- Willem de Bruijn
Credits
=======
- Björn Töpel (AF_XDP core)
- Magnus Karlsson (AF_XDP core)
- Alexander Duyck
- Alexei Starovoitov
- Daniel Borkmann
- Jesper Dangaard Brouer
- John Fastabend
- Jonathan Corbet (LWN coverage)
- Michael S. Tsirkin
- Qi Z Zhang
- Willem de Bruijn
요약·해설
af_xdp.rst:1-855AF_XDP는 XDP가 선택한 ingress frame을 사용자 공간 UMEM으로 전달하고, RX·TX descriptor ring을 통해 고속으로 교환하는 socket address family입니다. 성능의 핵심은 packet data가 아니라 frame 소유권을 FILL·RX·TX·COMPLETION ring 사이에서 이동시키는 데 있습니다.
XDP redirect에서 frame 재사용까지의 두 방향 흐름입니다.
각 ring의 producer·consumer와 frame 소유권 이동을 정리했습니다.
Producer와 consumer는 서로 다른 index를 단독으로 갱신합니다.
고유한 장치·queue tuple 수가 UMEM ring pair 수를 결정합니다.
Flag가 설정된 경우에만 system call로 kernel을 깨웁니다.
Bind flag와 socket option의 역할을 한눈에 정리했습니다.
`XDP_PKT_CONTD`는 다음 descriptor에서 패킷이 이어지는지를 표시합니다.
Copy와 zero-copy의 frame 수 제한이 다릅니다.
Socket과 실제 ingress queue의 일치 여부를 먼저 확인합니다.
동일 buffer를 동시에 여러 ring에 넣으면 NIC read·write가 충돌합니다.