FastMM documentation
FastMM C++ API 0.6.0
Public headers, tier 1 and tier 2
Loading...
Searching...
No Matches
xdp_datagram_source.hpp
Go to the documentation of this file.
1#pragma once
2// AF_XDP datagram source (ADR-0015, section 3): one XDP socket per (interface, RX queue), each with
3// its own UMEM, fill and RX rings (the completion ring exists because bind requires it). A BPF
4// program per interface, loaded and attached over raw bpf(2) calls, redirects UDP datagrams for
5// the subscribed (group, port) pairs to the sockets and passes everything else to the kernel. A
6// kernel UDP socket per multicast subscription joins the group so IGMP reports go out; it reads
7// nothing. A unicast subscription is a local address (no join). Linux 5.11 or later; needs
8// CAP_NET_ADMIN, CAP_NET_RAW, CAP_BPF and CAP_IPC_LOCK.
9//
10// Single-threaded: open, poll and the stats calls belong to the net thread. poll() allocates
11// nothing and returns every RX descriptor it took to the fill ring before it returns, so payload
12// spans are valid only inside the handler call.
13#include "fastmm/core/config_macros.hpp"
14#include "fastmm/core/time.hpp"
16#include "fastmm/net/udp_frame.hpp"
17#include "fastmm/net/xdp_program.hpp"
18
19#include <sys/socket.h>
20
21#include <atomic>
22#include <cstddef>
23#include <cstdint>
24#include <span>
25#include <string>
26#include <string_view>
27#include <utility>
28#include <vector>
29
30namespace fastmm::net {
31
32// Attach and bind mode. Auto tries ZeroCopy, then NativeCopy, then Generic.
33// ZeroCopy native XDP (driver), socket bound with XDP_ZEROCOPY
34// NativeCopy native XDP, socket bound with XDP_COPY
35// Generic generic XDP (XDP_FLAGS_SKB_MODE), socket bound with XDP_COPY
36enum class XdpMode : std::uint8_t { Auto, ZeroCopy, NativeCopy, Generic };
37
38[[nodiscard]] constexpr std::string_view to_string(XdpMode m) noexcept {
39 switch (m) {
41 return "auto";
43 return "zerocopy";
45 return "native_copy";
47 return "generic";
48 }
49 return "?";
50}
51
52struct XdpSubscription {
53 std::string interface; // netdev name
54 std::uint32_t group = 0; // IPv4 multicast group or local unicast address, network byte order
55 std::uint16_t port = 0; // host byte order
56 std::uint32_t source = 0; // network byte order; 0 = any. Joins only: the XDP map ignores it.
57};
59struct XdpInterfaceQueues {
60 std::string interface;
61 std::vector<std::uint32_t> queues;
62};
64struct XdpConfig {
65 std::vector<XdpSubscription> subscriptions; // line = index, at most 256
66 // RX queues per interface. Unlisted: every RX queue the interface has once the program is
67 // attached (virtio_net, for one, adds queues for XDP and the host delivers on all of them).
68 std::vector<XdpInterfaceQueues> queues;
69 std::uint32_t frame_count = 4096; // UMEM frames per socket, power of two
70 std::uint32_t frame_size = 4096; // 2048 or 4096 (at most the page size)
71 std::uint32_t batch = 64; // RX descriptors per socket per poll
73 // SO_PREFER_BUSY_POLL, SO_BUSY_POLL and SO_BUSY_POLL_BUDGET on every socket, and a recvfrom
74 // on every poll that finds the RX ring empty (drives NAPI from this thread).
75 bool busy_poll = false;
76 int busy_poll_usec = 20;
78 // IPv4 header and UDP checksums in user space (a zero UDP checksum is accepted).
79 bool verify_udp_checksum = false;
80};
82struct XdpStats {
83 std::uint64_t datagrams = 0; // delivered to the handler
84 std::uint64_t bytes = 0; // payload bytes delivered
85 std::uint64_t bad_frames = 0; // failed the user-space checks (udp_frame.hpp)
86 std::uint64_t unmatched = 0; // valid, but no subscription on that interface
87 std::uint64_t fill_short = 0; // descriptors not returned because the fill ring was full
88};
90// XDP_STATISTICS of one socket, as of the last refresh_stats().
91struct XdpSocketStats {
92 unsigned ifindex = 0;
93 std::uint32_t queue = 0;
94 std::uint64_t rx_dropped = 0;
95 std::uint64_t rx_invalid_descs = 0;
96 std::uint64_t rx_ring_full = 0;
97 std::uint64_t rx_fill_ring_empty_descs = 0;
98};
100struct XdpInterfaceStatus {
101 std::string name;
102 unsigned ifindex = 0;
103 XdpMode mode = XdpMode::Auto; // the mode open() settled on
104 std::uint64_t fallback_packets = 0; // subscribed, but no socket on the RX queue: XDP_PASS
107// "" when the process has CAP_NET_ADMIN, CAP_NET_RAW, CAP_BPF (or CAP_SYS_ADMIN) and CAP_IPC_LOCK
108// in its effective set; otherwise names the missing ones and the setcap command.
109[[nodiscard]] std::string xdp_capability_error();
110// "" on Linux 5.11 or later; otherwise the reason.
111[[nodiscard]] std::string xdp_kernel_error();
112
113// The BPF objects of one interface: subscription hash map, XSKMAP, per-CPU fallback counter and
114// the program, plus its XDP link once attached. Closing the link fd detaches the program.
115class XdpFilter {
116 public:
117 XdpFilter() = default;
118 ~XdpFilter() { close(); }
119 XdpFilter(const XdpFilter&) = delete;
120 XdpFilter& operator=(const XdpFilter&) = delete;
121 XdpFilter(XdpFilter&& o) noexcept { swap(o); }
123 if (this != &o) {
125 swap(o);
126 }
127 return *this;
128 }
129
130 // Creates the maps (XSKMAP with `xsk_slots` entries), inserts `keys` and loads the program.
131 // Returns 0 or -errno; `err` names the failing step and carries the verifier log when the load
132 // fails.
133 int create(std::span<const xdp::Key> keys, std::uint32_t xsk_slots, std::string& err);
134 // BPF_LINK_CREATE (attach type BPF_XDP) on `ifindex` with XDP_FLAGS_DRV_MODE or
135 // XDP_FLAGS_SKB_MODE. Returns 0 or -errno: -EBUSY or -EEXIST when the interface already has an
136 // XDP program, -EOPNOTSUPP when the driver has no native XDP.
137 int attach(unsigned ifindex, bool generic) noexcept;
138 void detach() noexcept;
139 // xsks[queue] = xsk_fd. The socket must be bound.
140 int set_socket(std::uint32_t queue, int xsk_fd) noexcept;
141 // Sum of the per-CPU fallback counters.
142 int fallback_count(std::uint64_t& out) noexcept;
143 // BPF_PROG_TEST_RUN on `frame` (at least 14 bytes). Returns 0 or -errno; the XDP action in
144 // `action`.
145 int test_run(std::span<const std::byte> frame, std::uint32_t& action) noexcept;
146
147 [[nodiscard]] int prog_fd() const noexcept { return prog_fd_; }
148 [[nodiscard]] int link_fd() const noexcept { return link_fd_; }
149 void close() noexcept;
151 private:
152 void swap(XdpFilter& o) noexcept {
153 std::swap(subs_fd_, o.subs_fd_);
154 std::swap(xsks_fd_, o.xsks_fd_);
155 std::swap(fallback_fd_, o.fallback_fd_);
156 std::swap(prog_fd_, o.prog_fd_);
157 std::swap(link_fd_, o.link_fd_);
158 percpu_.swap(o.percpu_);
159 }
160
161 int subs_fd_ = -1;
162 int xsks_fd_ = -1;
163 int fallback_fd_ = -1;
164 int prog_fd_ = -1;
165 int link_fd_ = -1;
166 std::vector<std::uint64_t> percpu_; // one slot per possible CPU
167};
168
169namespace detail {
170
171// A single-producer or single-consumer AF_XDP ring shared with the kernel (libxdp's xsk_ring_*).
172struct XskRing {
173 std::atomic<std::uint32_t>* producer = nullptr;
174 std::atomic<std::uint32_t>* consumer = nullptr;
175 std::atomic<std::uint32_t>* flags = nullptr;
176 void* entries = nullptr;
177 std::uint32_t mask = 0;
178 std::uint32_t size = 0;
179 std::uint32_t cached_prod = 0;
180 std::uint32_t cached_cons = 0; // producer rings: consumer + size
181
182 // Consumer side: up to `max` entries starting at `idx`.
183 std::uint32_t peek(std::uint32_t max, std::uint32_t& idx) noexcept {
184 std::uint32_t avail = cached_prod - cached_cons;
185 if (avail < max) {
186 cached_prod = producer->load(std::memory_order_acquire);
187 avail = cached_prod - cached_cons;
188 }
189 const std::uint32_t n = avail < max ? avail : max;
190 idx = cached_cons;
191 cached_cons += n;
192 return n;
193 }
194 void release() noexcept { consumer->store(cached_cons, std::memory_order_release); }
195
196 // Producer side: `n` slots starting at `idx`, or 0 when fewer are free.
197 std::uint32_t reserve(std::uint32_t n, std::uint32_t& idx) noexcept {
198 if (cached_cons - cached_prod < n) {
199 cached_cons = consumer->load(std::memory_order_acquire) + size;
200 if (cached_cons - cached_prod < n) return 0;
201 }
202 idx = cached_prod;
203 cached_prod += n;
204 return n;
205 }
206 void submit() noexcept { producer->store(cached_prod, std::memory_order_release); }
207 [[nodiscard]] bool needs_wakeup() const noexcept {
208 return (flags->load(std::memory_order_relaxed) & 1U) != 0; // XDP_RING_NEED_WAKEUP
209 }
210};
211
212struct XdpDescView {
213 std::uint64_t addr;
214 std::uint32_t len;
215 std::uint32_t options;
216};
217
218struct XdpRoute {
219 std::uint32_t dst_ip; // network byte order
220 std::uint16_t dst_port; // host byte order
221 std::uint8_t line;
222};
223
224struct XskSocket {
225 int fd = -1;
226 unsigned ifindex = 0;
227 std::uint32_t queue = 0;
228 std::uint32_t routes_begin = 0; // this interface's routes in XdpDatagramSource::routes_
229 std::uint32_t routes_end = 0;
230 std::byte* umem = nullptr;
231 std::size_t umem_len = 0;
232 void* fill_map = nullptr;
233 std::size_t fill_map_len = 0;
234 void* rx_map = nullptr;
235 std::size_t rx_map_len = 0;
236 XskRing fill;
237 XskRing rx;
238};
239
240} // namespace detail
241
242class XdpDatagramSource {
243 public:
244 XdpDatagramSource() = default;
251 // Returns 0 or -errno; error() explains. Fails with -EPERM and the setcap command when
252 // capabilities are missing, -ENOSYS before Linux 5.11, -EBUSY when an interface already has an
253 // XDP program. Never falls back to the kernel backend.
254 int open(const XdpConfig& cfg);
255 void close() noexcept;
256 [[nodiscard]] const std::string& error() const noexcept { return error_; }
257 // Busy-poll options the kernel refused at open (the sockets poll from user space without them).
258 [[nodiscard]] std::span<const std::string> warnings() const noexcept { return warnings_; }
259
260 // Delivers up to `batch` datagrams per socket; returns the number delivered.
261 template <class H>
262 std::size_t poll(H&& handler) noexcept {
263 std::size_t total = 0;
264 for (auto& s : sockets_) total += poll_socket(s, handler);
265 return total;
266 }
267
268 // XDP socket fds, readable when their RX ring has descriptors (adaptive mode waits on them).
269 [[nodiscard]] std::span<const int> fds() const noexcept { return fds_; }
270
271 // Reads XDP_STATISTICS and the fallback counters (system calls; not for the hot loop).
272 int refresh_stats() noexcept;
273 [[nodiscard]] const XdpStats& stats() const noexcept { return stats_; }
274 [[nodiscard]] std::span<const XdpSocketStats> socket_stats() const noexcept {
275 return socket_stats_;
277 [[nodiscard]] std::span<const XdpInterfaceStatus> interfaces() const noexcept {
278 return interfaces_;
280 // The filter of interfaces()[i] (tests run BPF_PROG_TEST_RUN on the attached program).
281 [[nodiscard]] XdpFilter& filter(std::size_t i) noexcept { return filters_[i]; }
282
283 private:
284 template <class H>
285 std::size_t poll_socket(detail::XskSocket& s, H& handler) noexcept {
286 std::uint32_t rx_idx = 0;
287 const std::uint32_t n = s.rx.peek(batch_, rx_idx);
288 if (n == 0) {
289 if (busy_poll_ || s.fill.needs_wakeup()) wake(s);
290 return 0;
291 }
292 RxMeta meta{};
293 meta.t0_cycles = rdtscp();
294 meta.t0_wall_ns = wall_now().ns;
295 std::uint32_t fill_idx = 0;
296 const bool can_fill = s.fill.reserve(n, fill_idx) == n;
297 const auto* descs = static_cast<const detail::XdpDescView*>(s.rx.entries);
298 auto* fill_addrs = static_cast<std::uint64_t*>(s.fill.entries);
299 std::size_t delivered = 0;
300 for (std::uint32_t i = 0; i < n; ++i) {
301 const detail::XdpDescView d = descs[(rx_idx + i) & s.rx.mask];
302 if (can_fill) fill_addrs[(fill_idx + i) & s.fill.mask] = d.addr & frame_mask_;
303 if (FASTMM_UNLIKELY(d.addr > s.umem_len || d.len > s.umem_len - d.addr)) {
304 ++stats_.bad_frames;
305 continue;
306 }
307 const std::span<const std::byte> frame(s.umem + d.addr, d.len);
308 const UdpFrame f = parse_udp_frame(frame, verify_checksums_);
309 if (FASTMM_UNLIKELY(f.status != FrameStatus::Ok)) {
310 ++stats_.bad_frames;
311 continue;
312 }
313 const detail::XdpRoute* route = nullptr;
314 for (std::uint32_t r = s.routes_begin; r < s.routes_end; ++r) {
315 if (routes_[r].dst_ip == f.dst_ip && routes_[r].dst_port == f.dst_port) {
316 route = &routes_[r];
317 break;
318 }
319 }
320 if (FASTMM_UNLIKELY(route == nullptr)) {
321 ++stats_.unmatched;
322 continue;
323 }
324 meta.src_ip = f.src_ip;
325 meta.dst_ip = f.dst_ip;
326 meta.dst_port = f.dst_port;
327 meta.line = route->line;
328 handler(f.payload, static_cast<const RxMeta&>(meta));
329 ++delivered;
330 stats_.bytes += f.payload.size();
331 }
332 s.rx.release();
333 if (can_fill) {
334 s.fill.submit();
335 } else {
336 stats_.fill_short += n; // cannot happen: the fill ring holds every frame of the UMEM
337 }
338 stats_.datagrams += delivered;
339 return delivered;
340 }
341
342 static void wake(const detail::XskSocket& s) noexcept {
343 ::recvfrom(s.fd, nullptr, 0, MSG_DONTWAIT, nullptr, nullptr);
344 }
345
346 int fail(int err, std::string msg);
347 int open_interface(std::size_t iface,
348 std::span<const std::uint32_t> listed,
349 std::uint32_t routes_begin,
350 std::uint32_t routes_end,
351 const XdpConfig& cfg);
352 int create_socket(detail::XskSocket& s, bool zerocopy, const XdpConfig& cfg, std::string& why);
353 static void close_socket(detail::XskSocket& s) noexcept;
354
355 std::vector<detail::XskSocket> sockets_;
356 std::vector<detail::XdpRoute> routes_;
357 std::vector<int> fds_;
358 std::vector<XdpFilter> filters_;
359 std::vector<int> join_fds_;
360 std::vector<XdpInterfaceStatus> interfaces_;
361 std::vector<XdpSocketStats> socket_stats_;
362 std::vector<std::string> warnings_;
363 std::string error_;
364 XdpStats stats_;
365 std::uint64_t frame_mask_ = 0;
366 std::uint32_t batch_ = 64;
367 bool busy_poll_ = false;
368 bool verify_checksums_ = false;
369};
370
371static_assert(DatagramSource<XdpDatagramSource>);
372
373} // namespace fastmm::net
Definition xdp_datagram_source.hpp:244
std::span< const XdpSocketStats > socket_stats() const noexcept
Definition xdp_datagram_source.hpp:276
XdpFilter & filter(std::size_t i) noexcept
The filter of interfaces()[i] (tests run BPF_PROG_TEST_RUN on the attached program).
Definition xdp_datagram_source.hpp:283
std::span< const int > fds() const noexcept
XDP socket fds, readable when their RX ring has descriptors (adaptive mode waits on them).
Definition xdp_datagram_source.hpp:271
std::size_t poll(H &&handler) noexcept
Delivers up to batch datagrams per socket; returns the number delivered.
Definition xdp_datagram_source.hpp:264
std::span< const std::string > warnings() const noexcept
Busy-poll options the kernel refused at open (the sockets poll from user space without them).
Definition xdp_datagram_source.hpp:260
XdpDatagramSource(const XdpDatagramSource &)=delete
XdpDatagramSource & operator=(const XdpDatagramSource &)=delete
int refresh_stats() noexcept
Reads XDP_STATISTICS and the fallback counters (system calls; not for the hot loop).
std::span< const XdpInterfaceStatus > interfaces() const noexcept
Definition xdp_datagram_source.hpp:279
int open(const XdpConfig &cfg)
Returns 0 or -errno; error() explains.
~XdpDatagramSource()
Definition xdp_datagram_source.hpp:247
const std::string & error() const noexcept
Definition xdp_datagram_source.hpp:258
const XdpStats & stats() const noexcept
Definition xdp_datagram_source.hpp:275
The BPF objects of one interface: subscription hash map, XSKMAP, per-CPU fallback counter and the pro...
Definition xdp_datagram_source.hpp:117
void detach() noexcept
int set_socket(std::uint32_t queue, int xsk_fd) noexcept
xsks[queue] = xsk_fd. The socket must be bound.
XdpFilter & operator=(const XdpFilter &)=delete
int create(std::span< const xdp::Key > keys, std::uint32_t xsk_slots, std::string &err)
Creates the maps (XSKMAP with xsk_slots entries), inserts keys and loads the program.
XdpFilter(const XdpFilter &)=delete
int fallback_count(std::uint64_t &out) noexcept
Sum of the per-CPU fallback counters.
int test_run(std::span< const std::byte > frame, std::uint32_t &action) noexcept
BPF_PROG_TEST_RUN on frame (at least 14 bytes).
int prog_fd() const noexcept
Definition xdp_datagram_source.hpp:149
int link_fd() const noexcept
Definition xdp_datagram_source.hpp:150
int attach(unsigned ifindex, bool generic) noexcept
BPF_LINK_CREATE (attach type BPF_XDP) on ifindex with XDP_FLAGS_DRV_MODE or XDP_FLAGS_SKB_MODE.
~XdpFilter()
Definition xdp_datagram_source.hpp:120
void close() noexcept
Datagram receive interface for multicast market data (ADR-0015, section 1).
Definition reactor.hpp:37
XdpMode
Attach and bind mode.
Definition xdp_datagram_source.hpp:38
std::string_view to_string(ReactorBackend backend) noexcept
"epoll" / "io_uring", the spelling of [engine] net_backend.
std::string xdp_capability_error()
"" when the process has CAP_NET_ADMIN, CAP_NET_RAW, CAP_BPF (or CAP_SYS_ADMIN) and CAP_IPC_LOCK in it...
std::string xdp_kernel_error()
"" on Linux 5.11 or later; otherwise the reason.
constexpr Fixed< T > max(Fixed< T > a, Fixed< T > b) noexcept
Definition fixed_point.hpp:418
Timestamp wall_now() noexcept
CLOCK_REALTIME via vDSO (~20 ns). Implemented in src/core/time.cpp.
FASTMM_FORCE_INLINE Cycles rdtscp() noexcept
Serialising read (rdtscp) is what latency hops want: it waits for prior loads to retire.
Definition time.hpp:66
std::int64_t ns
Definition time.hpp:46
Definition datagram_source.hpp:22
Cycles t0_cycles
rdtscp after the batch left the kernel or the RX ring
Definition datagram_source.hpp:23
Definition xdp_datagram_source.hpp:66
int busy_poll_budget
Definition xdp_datagram_source.hpp:79
bool verify_udp_checksum
IPv4 header and UDP checksums in user space (a zero UDP checksum is accepted).
Definition xdp_datagram_source.hpp:81
std::uint32_t frame_size
2048 or 4096 (at most the page size)
Definition xdp_datagram_source.hpp:72
int busy_poll_usec
Definition xdp_datagram_source.hpp:78
std::vector< XdpSubscription > subscriptions
line = index, at most 256
Definition xdp_datagram_source.hpp:67
bool busy_poll
SO_PREFER_BUSY_POLL, SO_BUSY_POLL and SO_BUSY_POLL_BUDGET on every socket, and a recvfrom on every po...
Definition xdp_datagram_source.hpp:77
std::uint32_t frame_count
UMEM frames per socket, power of two.
Definition xdp_datagram_source.hpp:71
std::uint32_t batch
RX descriptors per socket per poll.
Definition xdp_datagram_source.hpp:73
XdpMode mode
Definition xdp_datagram_source.hpp:74
std::vector< XdpInterfaceQueues > queues
RX queues per interface.
Definition xdp_datagram_source.hpp:70
Definition xdp_datagram_source.hpp:61
std::vector< std::uint32_t > queues
Definition xdp_datagram_source.hpp:63
std::string interface
Definition xdp_datagram_source.hpp:62
Definition xdp_datagram_source.hpp:102
XdpMode mode
the mode open() settled on
Definition xdp_datagram_source.hpp:105
unsigned ifindex
Definition xdp_datagram_source.hpp:104
std::string name
Definition xdp_datagram_source.hpp:103
std::uint64_t fallback_packets
subscribed, but no socket on the RX queue: XDP_PASS
Definition xdp_datagram_source.hpp:106
XDP_STATISTICS of one socket, as of the last refresh_stats().
Definition xdp_datagram_source.hpp:93
std::uint64_t rx_invalid_descs
Definition xdp_datagram_source.hpp:97
std::uint64_t rx_fill_ring_empty_descs
Definition xdp_datagram_source.hpp:99
std::uint64_t rx_ring_full
Definition xdp_datagram_source.hpp:98
unsigned ifindex
Definition xdp_datagram_source.hpp:94
std::uint32_t queue
Definition xdp_datagram_source.hpp:95
std::uint64_t rx_dropped
Definition xdp_datagram_source.hpp:96
Definition xdp_datagram_source.hpp:84
std::uint64_t unmatched
valid, but no subscription on that interface
Definition xdp_datagram_source.hpp:88
std::uint64_t fill_short
descriptors not returned because the fill ring was full
Definition xdp_datagram_source.hpp:89
std::uint64_t datagrams
delivered to the handler
Definition xdp_datagram_source.hpp:85
std::uint64_t bytes
payload bytes delivered
Definition xdp_datagram_source.hpp:86
std::uint64_t bad_frames
failed the user-space checks (udp_frame.hpp)
Definition xdp_datagram_source.hpp:87
std::string interface
netdev name
Definition xdp_datagram_source.hpp:55
std::uint32_t group
IPv4 multicast group or local unicast address, network byte order.
Definition xdp_datagram_source.hpp:56
std::uint32_t source
network byte order; 0 = any. Joins only: the XDP map ignores it.
Definition xdp_datagram_source.hpp:58
std::uint16_t port
host byte order
Definition xdp_datagram_source.hpp:57
Time primitives.