Re: XDP CPU maps & shared umem
Magnus Karlsson <[email protected]> Tue, 28 May 2024 14:21:43 +0200
| Newsgroups | org.kernel.vger.xdp-newbies |
|---|---|
| Message-ID | <CAJ8uoz0uokxmUYWrFsxVSCUAdz5vGU8DP6Q7mX=Dq=SMu2-xKw@mail.gmail.com> |
On Mon, 27 May 2024 at 10:11, Magnus Karlsson <[email protected]> w= rote: > > On Mon, 27 May 2024 at 09:34, Yuval El-Hanany <[email protected]> wrote: > > > > Yeah, > > as far down as I dug in the code, it seems it=E2=80=99s the onl= y one that=E2=80=99s used. The other members are reg_state & napi_id. I=E2= =80=99m unfamiliar with their exact purposes, but doesn=E2=80=99t sound lik= e they=E2=80=99re necessary down the line. One other concern is txq IMO. It= is not initialised to any value in xdp_buff. While it may not be relevant = considering it=E2=80=99s a received packet, it seems wrong to keep a dangli= ng pointer both in terms of security (leaking previous values) and robustne= ss. Maybe just set it to NULL? > > > > I=E2=80=99ve been racking my brain as to what is the accepted m= ethod to pass the receive queue. Passing it on the packet headroom seemed l= ike a reasonable solution, but it would mean memory access to the packet me= mory on packet arrival on the second CPU which may be harmful to performanc= e. The queue itself is just a pointer queue so can=E2=80=99t piggyback on i= t unless using a different kind of queue. Would be great to hear how it sho= uld be done. > > Let me get into this piece of code and scratch my head for a while and > I will get back to you. Do not have a solution on top of my head. Will > loop in Maciej too. Yuval, Could you please send me the patch that you are using? I cannot remember what I sent you :-). > /Magnus > > > Thanks, > > Yuval. > > > > > On 27 May 2024, at 8:48, Magnus Karlsson <[email protected]> = wrote: > > > > > > CAUTION:External Email, Do not click on links or open attachments unl= ess you recognize the sender and know the content is safe. > > > > > > On Sun, 26 May 2024 at 14:48, Yuval El-Hanany <[email protected]> wr= ote: > > >> > > >> Correction, > > >> the rxq pointer is init=E2=80=99d globally for all frames bef= ore the frame loops, but only the device and the memory information are set= from the XDP frame. So still an issue with the queue_index, but the tests = on the device and memory info in xsk_rcv should be fine. > > >> > > >> Cheers, > > >> Yuval. > > > > > > Hi Yuval, > > > > > > Now I am back. Yes, this seems to be a bug so thank you for spotting > > > it. This is nothing I have ever tested since I thought you would do a= n > > > XSKMAP redirect to the "right" core first. But there is nothing > > > hindering that someone would do a CPUMAP redirect first then an XSKMA= P > > > redirect, so should be supported. So in your analysis, is it only > > > queue_index that needs to be populated at CPUMAP redirect time for > > > this to work? > > > > > > Thanks: Magnus > > > > > >>> On 26 May 2024, at 12:02, Yuval El-Hanany <[email protected]> wrot= e: > > >>> > > >>> Hi, > > >>> so I=E2=80=99ve dug a bit deeper. It seems the receive queue is not= maintained across CPUs and this may make the whole practice of redirecting= to XSK unsafe if I read the code correctly. It also seems this persists to= kernel 6.6.8. Am I missing something? > > >>> > > >>> The code in cpumap runs XDP programs BUT doesn=E2=80=99t fill the r= xq (see also the TODO comment in the code). The XDP frame is converted to X= DP buffer but the rxq and txq are not filled. It seems they remain as dangl= ing pointers. When __xsk_map_redirect is called and in turn xsk_rcv the rxq= is consulted both to check if the umem is shared (in the new patch) as wel= l as decide whether zero copy is used or not. Since rxq is not valid, I=E2= =80=99m not certain what the outcome is. Once I bypassed umem check, I no l= onger crash the kernel but I'm definitely not seeing traffic running. > > >>> > > >>> Thanks, > > >>> Yuval. > > >>> > > >>> Here are the excerpts from the code (5.15.117): > > >>> > > >>> cpumap.c:212 <cpu_map_bpf_prog_run_xdp> =3D=3D=3D=3D=3D Note the co= mment in the code. =3D=3D=3D=3D=3D > > >>> for (i =3D 0; i < n; i++) { > > >>> struct xdp_frame *xdpf =3D frames[i]; > > >>> u32 act; > > >>> int err; > > >>> > > >>> rxq.dev =3D xdpf->dev_rx; > > >>> rxq.mem =3D xdpf->mem; > > >>> /* TODO: report queue_index to xdp_rxq_info */ > > >>> > > >>> xdp_convert_frame_to_buff(xdpf, &xdp); > > >>> > > >>> act =3D bpf_prog_run_xdp(rcpu->prog, &xdp); > > >>> > > >>> xdp.h:175 =3D=3D=3D=3D=3D rxq & txq in the xdp_buff are not filled = or even reset. =3D=3D=3D=3D=3D > > >>> static inline > > >>> void xdp_convert_frame_to_buff(struct xdp_frame *frame, struct xdp_= buff *xdp) > > >>> { > > >>> xdp->data_hard_start =3D frame->data - frame->headroom - sizeof(*= frame); > > >>> xdp->data =3D frame->data; > > >>> xdp->data_end =3D frame->data + frame->len; > > >>> xdp->data_meta =3D frame->data - frame->metasize; > > >>> xdp->frame_sz =3D frame->frame_sz; > > >>> } > > >>> > > >>> xsk.c:269 =3D=3D=3D=3D=3D xsk_rcv consults rxq in xdp_buff which is= not filled! =3D=3D=3D=3D=3D > > >>> static int xsk_rcv(struct xdp_sock *xs, struct xdp_buff *xdp) > > >>> { > > >>> int err; > > >>> u32 len; > > >>> > > >>> err =3D xsk_rcv_check(xs, xdp); > > >>> if (err) > > >>> return err; > > >>> > > >>> if (xdp->rxq->mem.type =3D=3D MEM_TYPE_XSK_BUFF_POOL) { > > >>> len =3D xdp->data_end - xdp->data; > > >>> return __xsk_rcv_zc(xs, xdp, len); > > >>> } > > >>> > > >>> err =3D __xsk_rcv(xs, xdp); > > >>> if (!err) > > >>> xdp_return_buff(xdp); > > >>> return err; > > >>> } > > >>> > > >>> int __xsk_map_redirect(struct xdp_sock *xs, struct xdp_buff *xdp) > > >>> { > > >>> struct list_head *flush_list =3D this_cpu_ptr(&xskmap_flush_list)= ; > > >>> int err; > > >>> > > >>> err =3D xsk_rcv(xs, xdp); > > >>> if (err) > > >>> return err; > > >>> > > >>> if (!xs->flush_node.prev) > > >>> list_add(&xs->flush_node, flush_list); > > >>> > > >>> return 0; > > >>> } > > >>> > > >>>> On 24 May 2024, at 18:49, Yuval El-Hanany <[email protected]> wro= te: > > >>>> > > >>>> CAUTION:External Email, Do not click on links or open attachments = unless you recognize the sender and know the content is safe. > > >>>> > > >>>> Hi All, > > >>>> I would really appreciate some guidance here. As this issue s= pans both network XDP and CPU map, it=E2=80=99s a bit more difficult for me= to follow in the kernel code. I attempted to trace the issue in the source= , but it=E2=80=99s a bit complex to enter existing code base. > > >>>> > > >>>> It will be easier if you can tell me the following: > > >>>> 1. Is the receive queue metadata maintained correctly through the = CPU switch in CPU map? In the description of CPU map in XDP (https://develo= pers.redhat.com/blog/2021/05/13/receive-side-scaling-rss-with-ebpf-and-cpum= ap), it is mentioned that some metadata is not maintained across the CPU sw= itch, but that would make the umem test problematic. Should I assume the re= ceive queue is maintained? > > >>>> 2. Does XSK umem user code have access to the receive queue metada= ta memory? It seems to be in a different pointer, but it could be that it p= oints to the packet headroom in the umem accessible memory. > > >>>> > > >>>> If the receive queue is not maintained across CPU switch then= I think the shared umem test should run along the lines of using some defa= ult receive queue after switching CPUs. Otherwise, it should probably drop = packets with invalid receive queues. > > >>>> > > >>>> Thanks, > > >>>> Yuval. > > >>>> > > >>>>> On 20 May 2024, at 18:24, Yuval El-Hanany <[email protected]> wr= ote: > > >>>>> > > >>>>> Hi, > > >>>>> > > >>>>> Short Version > > >>>>> I=E2=80=99ve encountered an issue redirecting traffic to another = CPU while using shared UMEM. The frame arrived at the redirected CPU with i= nvalid receive queue. This caused a kernel crash on the patch that supports= redirecting to packets to any queue with the same umem due to a test based= on the receive queue. > > >>>>> > > >>>>> Long Version > > >>>>> I=E2=80=99ve created a test program that redirects traffic betwee= n two interfaces (replacing the MAC address). See the code below. It starts= two separate processes on two different cores. When directing traffic dire= ctly to an XSK socket all works well. I=E2=80=99m running on kernel 5.15.11= 7 with Magnus patch for shared umem. > > >>>>> > > >>>>> The next step was an XDP program that redirects each packet to th= e other CPU, and the xdp CPU program redirect the packets to an XDP socket.= Initially this resulted in a kernel crash. The reason is that the receive = queue is not received correctly once the packet switches CPU. > > >>>>> > > >>>>> I=E2=80=99ve patched the kernel so it does sanity check and does = not crash but defaults to queue 0 just for the umem test. > > >>>>> > > >>>>> The kernel no longer crashes but I don=E2=80=99t get the traffic = to the XSK sockets, and I log the receive queues and they do not seem rando= m: > > >>>>> On first interface: > > >>>>> 0xC1323D50 > > >>>>> 0xC1343D19 > > >>>>> 0xC1343D50 > > >>>>> > > >>>>> On second interface: > > >>>>> 0xC134BD50 > > >>>>> 0xC135BD50 > > >>>>> > > >>>>> Very few bits differences between each of the =E2=80=9Creceive qu= eues=E2=80=9D. I couldn=E2=80=99t really find something in my program with = those or similar values, or in the packets so it makes it less likely it co= mes from something I do in userspace. Seems to suggest some flags field? Th= e values are not always the same across reboots but pretty close. > > >>>>> > > >>>>> Am I doing something wrong? It seems pretty basic. Most of the co= de in the XDP program was just added for debugging. The only oddity perhaps= is using the shared umem. > > >>>>> > > >>>>> Thanks, > > >>>>> Yuval. > > >>>>> > > >>>>> I=E2=80=99m attaching the program and values from my "debug map= =E2=80=9D. In the debug map: > > >>>>> 0. Values 0/1 - Which type of program ran (with/without CPU redir= ection). > > >>>>> 1. Values 2XY - packet redirected from core X to core Y (1st stag= e). > > >>>>> 2. Values 4XY - packet on core X returns action Y (1st stage). > > >>>>> (These suggest that 1st stage worked correctly). > > >>>>> 3. Values 1XXYY - packet receive queue XX on core YY (XX =3D 10 i= s invalid receive queue outside 2..5). > > >>>>> 4. 2XXXX - packet receive queue on any core (XXXX - 4999 imeans q= ueue greater than 5000). > > >>>>> 5. Random entry - a receive queue. > > >>>>> > > >>>>> Debug map values without CPU redirection: > > >>>>> +------+------+--------------+ > > >>>>> | Port | Key | Received | > > >>>>> +------+------+--------------+ > > >>>>> | 0 | 1032 | 504 | > > >>>>> | 0 | 1024 | 37005992 | > > >>>>> | 0 | 1034 | 37544052 | > > >>>>> | 0 | 0 | 74546875 | > > >>>>> | 1 | 1034 | 35765961 | > > >>>>> | 1 | 0 | 71492773 | > > >>>>> | 1 | 1024 | 35729107 | > > >>>>> | 1 | 1032 | 277 | > > >>>>> +------+------+--------------+ > > >>>>> > > >>>>> Debug map value with CPU redirection (no other change): > > >>>>> +------+------+--------------+ > > >>>>> | Port | Key | Received | > > >>>>> +------+------+--------------+ > > >>>>> | 0 | 1 | 3400 | > > >>>>> | 0 | 223 | 2947 | > > >>>>> | 0 | 232 | 454 | > > >>>>> | 0 | 424 | 454 | > > >>>>> | 0 | 434 | 2947 | > > >>>>> | 0 | 11002 | 454 | > > >>>>> | 0 | 11003 | 2947 | > > >>>>> | 0 | 20000 | 106 | > > >>>>> | 0 | 20016 | 7 | > > >>>>> | 0 | 20024 | 1058 | > > >>>>> | 0 | 20582 | 2 | > > >>>>> | 0 | 24999 | 2231 | > > >>>>> | 0 | 3241295184 | 339 | > > >>>>> | 0 | 3241426201 | 3 | > > >>>>> | 0 | 3241426256 | 1891 | > > >>>>> | 1 | 1 | 1013 | > > >>>>> | 1 | 223 | 610 | > > >>>>> | 1 | 232 | 404 | > > >>>>> | 1 | 424 | 404 | > > >>>>> | 1 | 434 | 610 | > > >>>>> | 1 | 11002 | 404 | > > >>>>> | 1 | 11003 | 610 | > > >>>>> | 1 | 20000 | 17 | > > >>>>> | 1 | 20024 | 309 | > > >>>>> | 1 | 24999 | 689 | > > >>>>> | 1 | 3241459024 | 309 | > > >>>>> | 1 | 3241524560 | 381 | > > >>>>> +------+------+--------------+ > > >>>>> > > >>>>> The XDP code (#if 0 is for the no CPU redirection): > > >>>>> /* SPDX-License-Identifier: GPL-2.0 */ > > >>>>> > > >>>>> #include <linux/if_ether.h> > > >>>>> #include <linux/bpf.h> > > >>>>> #include <linux/in.h> > > >>>>> > > >>>>> #include <bpf/bpf_helpers.h> > > >>>>> #include <bpf/bpf_endian.h> > > >>>>> > > >>>>> #include "xdp/parsing_helpers.h" > > >>>>> > > >>>>> /* Redirect to SP queues map. */ > > >>>>> struct > > >>>>> { > > >>>>> __uint (type, BPF_MAP_TYPE_XSKMAP); > > >>>>> __type (key, __u32); > > >>>>> __type (value, __u32); > > >>>>> __uint (max_entries, 128); > > >>>>> } xsks_map SEC(".maps"); > > >>>>> > > >>>>> /* Redirect to CPUs map. */ > > >>>>> struct > > >>>>> { > > >>>>> __uint (type, BPF_MAP_TYPE_CPUMAP); > > >>>>> __uint (key_size, sizeof (__u32)); > > >>>>> __uint (value_size, sizeof (struct bpf_cpumap_val)); > > >>>>> __uint (max_entries, 32); > > >>>>> } cpu_map SEC(".maps"); > > >>>>> > > >>>>> /* Statistics and debug maps. */ > > >>>>> struct > > >>>>> { > > >>>>> __uint (type, BPF_MAP_TYPE_PERCPU_ARRAY); > > >>>>> __type (key, __u32); > > >>>>> __type (value, __u32); > > >>>>> __uint (max_entries, 64); > > >>>>> } xdp_stats_map SEC(".maps"); > > >>>>> > > >>>>> struct > > >>>>> { > > >>>>> __uint (type, BPF_MAP_TYPE_HASH); > > >>>>> __type (key, __u32); > > >>>>> __type (value, __u32); > > >>>>> __uint (max_entries, 64); > > >>>>> } xdp_debug_proto SEC(".maps"); > > >>>>> > > >>>>> static __u32 oneval =3D 1; > > >>>>> > > >>>>> int my_parse_ethhdr (struct hdr_cursor* nh, void* dataend, struct= ethhdr** eth) > > >>>>> { > > >>>>> __u16 ethtype; > > >>>>> *eth =3D nh->pos; > > >>>>> if ((void*) &(*eth) [1] > dataend) > > >>>>> { > > >>>>> return (-1); > > >>>>> } > > >>>>> > > >>>>> ethtype =3D (*eth)->h_proto; > > >>>>> nh->pos =3D &(*eth) [1]; > > >>>>> if (ethtype =3D=3D bpf_ntohs (ETH_P_8021Q)) > > >>>>> { > > >>>>> struct vlan_hdr* vlan =3D (struct vlan_hdr*) &(*eth) [1]; > > >>>>> if ((void*) &vlan [1] > dataend) > > >>>>> { > > >>>>> return (-1); > > >>>>> } > > >>>>> ethtype =3D vlan->h_vlan_encapsulated_proto; > > >>>>> /* inc_debug_map (10000+(bpf_ntohs (vlan->h_vlan_TCI) & 0x0FFF));= */ > > >>>>> /* inc_debug_map (vlan->h_vlan_TCI); */ > > >>>>> nh->pos =3D &vlan [1]; > > >>>>> } > > >>>>> > > >>>>> return (ethtype); > > >>>>> } > > >>>>> > > >>>>> static void inc_debug_map (__u32 key) > > >>>>> { > > >>>>> #ifdef WITH_STATS > > >>>>> bpf_map_update_elem (&xdp_debug_proto, > > >>>>> &key, (__u32*) &oneval, BPF_NOEXIST); > > >>>>> __u32* rec =3D bpf_map_lookup_elem (&xdp_debug_proto, (__u32*) &= key); > > >>>>> if (rec !=3D NULL) > > >>>>> { > > >>>>> ++(*rec); > > >>>>> } > > >>>>> #endif > > >>>>> } > > >>>>> > > >>>>> > > >>>>> > > >>>>> SEC ("xdp") > > >>>>> int rdwr_xsk_prog (struct xdp_md *ctx) > > >>>>> { > > >>>>> enum xdp_action action; > > >>>>> int index =3D ctx->rx_queue_index; > > >>>>> void* dataend =3D (void *)(long)ctx->data_end; > > >>>>> void* data =3D (void *)(long)ctx->data; > > >>>>> int stage =3D 0; > > >>>>> > > >>>>> struct hdr_cursor nh; > > >>>>> > > >>>>> __u32 *pkt_count; > > >>>>> struct ethhdr* eth; > > >>>>> > > >>>>> #ifdef WITH_STATS > > >>>>> pkt_count =3D bpf_map_lookup_elem(&xdp_stats_map, &index); > > >>>>> if (pkt_count !=3D NULL) > > >>>>> { > > >>>>> ++*pkt_count; > > >>>>> } > > >>>>> #endif > > >>>>> > > >>>>> /* Parse Ethernet & VLAN */ > > >>>>> nh.pos =3D data; > > >>>>> int ethtype =3D my_parse_ethhdr (&nh, dataend, ð); > > >>>>> if (ethtype !=3D bpf_ntohs (0x0800)) > > >>>>> { > > >>>>> return (XDP_PASS); > > >>>>> } > > >>>>> > > >>>>> /* Direct XSK redirect. */ > > >>>>> #if 0 > > >>>>> inc_debug_map (stage); > > >>>>> action =3D bpf_redirect_map (&xsks_map, index, XDP_PASS); > > >>>>> inc_debug_map (1000 + index*10 + action); > > >>>>> #endif > > >>>>> /* CPU map redirect. */ > > >>>>> __u32 cpuid =3D bpf_get_smp_processor_id (); > > >>>>> __u32 targetcpu =3D cpuid ^ 0x01; > > >>>>> ++stage; > > >>>>> inc_debug_map (stage); > > >>>>> inc_debug_map (200 + cpuid * 10 + targetcpu); > > >>>>> action =3D bpf_redirect_map (&cpu_map, targetcpu, XDP_PASS); > > >>>>> inc_debug_map (400 + targetcpu * 10 + action); > > >>>>> return (action); > > >>>>> } > > >>>>> > > >>>>> SEC ("xdp/cpumap") > > >>>>> int rdwr_cpu_prog(struct xdp_md *ctx) > > >>>>> { > > >>>>> __u64* dataend64 =3D (__u64*) (long) ctx->data_end; > > >>>>> __u64* data64 =3D (__u64*) (long) ctx->data; > > >>>>> __u32 rxqueue =3D ctx->rx_queue_index; > > >>>>> if (rxqueue < 5000) > > >>>>> { > > >>>>> inc_debug_map (20000 + rxqueue); > > >>>>> } > > >>>>> else > > >>>>> { > > >>>>> inc_debug_map (rxqueue); > > >>>>> inc_debug_map (24999); > > >>>>> } > > >>>>> > > >>>>> rxqueue =3D (rxqueue >=3D 2 && rxqueue < 6) ? rxqueue : 10; > > >>>>> __u32 cpuid =3D bpf_get_smp_processor_id (); > > >>>>> cpuid =3D (cpuid < 12) ? cpuid : 20; > > >>>>> inc_debug_map (10000 + 100*rxqueue + cpuid); > > >>>>> > > >>>>> return bpf_redirect_map (&xsks_map, bpf_get_smp_processor_id (),= 0); > > >>>>> } > > >>>>> > > >>>> > > >>> > > >> > >