AF_XDP is a kernel bypass technology that can greatly improve performance.
However, for virtual devices like veth, even with the use of AF_XDP sockets,
there are still many additional software paths that consume CPU resources.
This patch series focuses on optimizing the performance of AF_XDP sockets
for veth virtual devices. Patches 1 to 4 mainly involve preparatory work.
Patch 5 introduces tx queue and tx napi for packet transmission, while
patch 9 primarily implements zero-copy, and patch 10 adds support for
batch sending of IPv4 UDP packets. These optimizations significantly reduce
the software path and support checksum offload.
I tested those feature with
A typical topology is shown below:
veth<-->veth-peer veth1-peer<--->veth1
1 | | 7
|2 6|
| |
bridge<------->eth0(mlnx5)- switch -eth1(mlnx5)<--->bridge1
3 4 5
(machine1) (machine2)
AF_XDP socket is attach to veth and veth1. and send packets to physical NIC(eth0)
veth:(172.17.0.2/24)
bridge:(172.17.0.1/24)
eth0:(192.168.156.66/24)
eth1(172.17.0.2/24)
bridge1:(172.17.0.1/24)
eth0:(192.168.156.88/24)
after set default routeãsnatãdnat. we can have a tests
to get the performance results.
packets send from veth to veth1:
af_xdp test tool:
link:https://github.com/cclinuxer/libxudp
send:(veth)
./objs/xudpperf send --dst 192.168.156.88:6002 -l 1300
recv:(veth1)
./objs/xudpperf recv --src 172.17.0.2:6002
udp test tool:iperf3
send:(veth)
iperf3 -c 192.168.156.88 -p 6002 -l 1300 -b 60G -u
recv:(veth1)
iperf3 -s -p 6002
performance:
performance:(test weth libxdp lib)
UDP : 250 Kpps (with 100% cpu)
AF_XDP no zerocopy + no batch : 480 Kpps (with ksoftirqd 100% cpu)
AF_XDP with zerocopy + no batch : 540 Kpps (with ksoftirqd 100% cpu)
AF_XDP with batch + zerocopy : 1.5 Mpps (with ksoftirqd 15% cpu)
With af_xdp batch, the libxdp user-space program reaches a bottleneck.
Therefore, the softirq did not reach the limit.
This is just an RFC patch series, and some code details still need
further consideration. Please review this proposal.
thanks!
huangjie.albert (10):
veth: Implement ethtool's get_ringparam() callback
xsk: add dma_check_skip for skipping dma check
veth: add support for send queue
xsk: add xsk_tx_completed_addr function
veth: use send queue tx napi to xmit xsk tx desc
veth: add ndo_xsk_wakeup callback for veth
sk_buff: add destructor_arg_xsk_pool for zero copy
xdp: add xdp_mem_type MEM_TYPE_XSK_BUFF_POOL_TX
veth: support zero copy for af xdp
veth: af_xdp tx batch support for ipv4 udp
drivers/net/veth.c | 729 +++++++++++++++++++++++++++++++++++-
include/linux/skbuff.h | 1 +
include/net/xdp.h | 1 +
include/net/xdp_sock_drv.h | 1 +
include/net/xsk_buff_pool.h | 1 +
net/xdp/xsk.c | 6 +
net/xdp/xsk_buff_pool.c | 3 +-
net/xdp/xsk_queue.h | 11 +
8 files changed, 751 insertions(+), 2 deletions(-)
--
2.20.1
some xsk libary calls get_ringparam() API to get the queue length
to init the xsk umem.
Implement that in veth so those scenarios can work properly.
Signed-off-by: huangjie.albert <redacted>
---
drivers/net/veth.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
@@ -81,6 +81,7 @@ struct xsk_buff_pool {booluses_need_wakeup;booldma_need_sync;boolunaligned;+booldma_check_skip;void*addrs;/* Mutual exclusion of the completion ring in the SKB mode. Two cases to protect:*NAPITXthreadandsendmsgerrorpathsintheSKBdestructorcallbackandwhen
@@ -202,7 +203,7 @@ int xp_assign_dev(struct xsk_buff_pool *pool,if(err)gotoerr_unreg_pool;-if(!pool->dma_pages){+if(!pool->dma_pages&&!pool->dma_check_skip){WARN(1,"Driver did not DMA map zero-copy buffers");err=-EINVAL;gotoerr_unreg_xsk;
@@ -1061,6 +1063,176 @@ static int veth_poll(struct napi_struct *napi, int budget)returndone;}+staticintveth_xsk_tx_xmit(structveth_sq*sq,structxsk_buff_pool*xsk_pool,intbudget)+{+structveth_priv*priv,*peer_priv;+structnet_device*dev,*peer_dev;+structveth_rq*peer_rq;+structveth_statspeer_stats={};+structveth_statsstats={};+structveth_xdp_tx_bqbq;+structxdp_descdesc;+void*xdpf;+intdone=0;++bq.count=0;+dev=sq->dev;+priv=netdev_priv(dev);+peer_dev=priv->peer;+peer_priv=netdev_priv(peer_dev);++/* todo: queue index must set before this */+peer_rq=&peer_priv->rq[sq->queue_index];++/* set xsk wake up flag, to do: where to disable */+if(xsk_uses_need_wakeup(xsk_pool))+xsk_set_tx_need_wakeup(xsk_pool);++while(budget-->0){+unsignedinttruesize=0;+structxdp_frame*p_frame;+structpage*page;+void*new_addr;+void*addr;++/*+*getadescfromxskpool+*/+if(!xsk_tx_peek_desc(xsk_pool,&desc)){+break;+}++/*+*Getaxmitaddr+*desc.addrisaoffset,soweshouldtoconverttorealvirtualaddress+*/+addr=xsk_buff_raw_get_data(xsk_pool,desc.addr);++/* can not hold all data in a page */+truesize=SKB_DATA_ALIGN(sizeof(structskb_shared_info))+desc.len+sizeof(structxdp_frame);+if(truesize>PAGE_SIZE){+stats.xdp_drops++;+xsk_tx_completed_addr(xsk_pool,desc.addr);+continue;+}++page=dev_alloc_page();+if(!page){+/*+*error,releasexdpframeandincreasedrops+*/+xsk_tx_completed_addr(xsk_pool,desc.addr);+stats.xdp_drops++;+break;+}+new_addr=page_to_virt(page);++p_frame=new_addr;+new_addr+=sizeof(structxdp_frame);+p_frame->data=new_addr;+p_frame->len=desc.len;++/* frame should change to the page size, beacause the (struct skb_shared_info) is so large,+*ifwebuildskbinveth_xdp_rcv_one,skb->tailmaylargerthanskb->endwhichcouldtrigeraskb_panic+*/+p_frame->headroom=0;+p_frame->metasize=0;+p_frame->frame_sz=PAGE_SIZE;+p_frame->flags=0;+p_frame->mem.type=MEM_TYPE_PAGE_SHARED;+memcpy(p_frame->data,addr,p_frame->len);+xsk_tx_completed_addr(xsk_pool,desc.addr);++/* if peer have xdp prog, if it has ,just send to peer */+p_frame=veth_xdp_rcv_one(peer_rq,p_frame,&bq,&peer_stats);+/* if no xdp with this queue, convert to skb to xmit*/+if(p_frame){+xdpf=p_frame;+veth_xdp_rcv_bulk_skb(peer_rq,&xdpf,1,&bq,&peer_stats);+p_frame=NULL;+}++stats.xdp_bytes+=desc.len;++done++;+}++/* release, move consumerï¼and wakeup the producer */+if(done){+napi_schedule(&peer_rq->xdp_napi);+xsk_tx_release(xsk_pool);+}++++/* just for peer rq */+if(peer_stats.xdp_tx>0)+veth_xdp_flush(peer_rq,&bq);+if(peer_stats.xdp_redirect>0)+xdp_do_flush();++/* update peer rq stats, or maybe we do not need to do this */+u64_stats_update_begin(&peer_rq->stats.syncp);+peer_rq->stats.vs.xdp_redirect+=peer_stats.xdp_redirect;+peer_rq->stats.vs.xdp_packets+=done;+peer_rq->stats.vs.xdp_bytes+=stats.xdp_bytes;+peer_rq->stats.vs.xdp_drops+=peer_stats.xdp_drops;+peer_rq->stats.vs.rx_drops+=peer_stats.rx_drops;+peer_rq->stats.vs.xdp_tx+=peer_stats.xdp_tx;+u64_stats_update_end(&peer_rq->stats.syncp);++/* update sq stats */+u64_stats_update_begin(&sq->stats.syncp);+sq->stats.vs.xdp_packets+=done;+sq->stats.vs.xdp_bytes+=stats.xdp_bytes;+sq->stats.vs.xdp_drops+=stats.xdp_drops;+u64_stats_update_end(&sq->stats.syncp);++returndone;+}++staticintveth_poll_tx(structnapi_struct*napi,intbudget)+{+structveth_sq*sq=container_of(napi,structveth_sq,xdp_napi);+structxsk_buff_pool*pool;+intdone=0;+xdp_set_return_frame_no_direct();++sq->xsk.last_cpu=smp_processor_id();++/* xmit for tx queue */+rcu_read_lock();+pool=rcu_dereference(sq->xsk.pool);+if(pool){+done=veth_xsk_tx_xmit(sq,pool,budget);+}+rcu_read_unlock();++if(done<budget){+/* if done < budget, the tx ring is no buffer */+napi_complete_done(napi,done);+}++xdp_clear_return_frame_no_direct();++returndone;+}+++staticintveth_napi_add_tx(structnet_device*dev)+{+structveth_priv*priv=netdev_priv(dev);+inti;++for(i=0;i<dev->real_num_rx_queues;i++){+structveth_sq*sq=&priv->sq[i];+netif_napi_add(dev,&sq->xdp_napi,veth_poll_tx);+napi_enable(&sq->xdp_napi);+}++return0;+}+staticintveth_create_page_pool(structveth_rq*rq){structpage_pool_paramspp_params={
@@ -1153,6 +1325,19 @@ static void veth_napi_del_range(struct net_device *dev, int start, int end)}}+staticvoidveth_napi_del_tx(structnet_device*dev)+{+structveth_priv*priv=netdev_priv(dev);+inti;++for(i=0;i<dev->real_num_rx_queues;i++){+structveth_sq*sq=&priv->sq[i];++napi_disable(&sq->xdp_napi);+__netif_napi_del(&sq->xdp_napi);+}+}+staticvoidveth_napi_del(structnet_device*dev){veth_napi_del_range(dev,0,dev->real_num_rx_queues);
@@ -1737,11 +1922,89 @@ static int veth_xdp_set(struct net_device *dev, struct bpf_prog *prog,returnerr;}+staticintveth_xsk_pool_enable(structnet_device*dev,structxsk_buff_pool*pool,u16qid)+{+structveth_priv*peer_priv;+structveth_priv*priv=netdev_priv(dev);+structnet_device*peer_dev=priv->peer;+interr=0;++if(qid>=dev->real_num_tx_queues)+return-EINVAL;++if(!peer_dev)+return-EINVAL;++/* no dma, so we just skip dma skip in xsk zero copy */+pool->dma_check_skip=true;++peer_priv=netdev_priv(peer_dev);+/*+*enablepeertxxdphere,thisside+*xdpisenablebyveth_xdp_set+*todo:weneedtocheckwhtherthissideisalreadyenablexdp+*maybeitdonothavexdpprog+*/+if(!(peer_priv->_xdp_prog)&&(!veth_gro_requested(peer_dev))){+/* peer should enable napi*/+err=veth_napi_enable(peer_dev);+if(err)+returnerr;+}++/* Here is already protected by rtnl_lock, so rcu_assign_pointer+*issafe.+*/+rcu_assign_pointer(priv->sq[qid].xsk.pool,pool);++veth_napi_add_tx(dev);++returnerr;+}++staticintveth_xsk_pool_disable(structnet_device*dev,u16qid)+{+structveth_priv*peer_priv;+structveth_priv*priv=netdev_priv(dev);+structnet_device*peer_dev=priv->peer;+interr=0;++if(qid>=dev->real_num_tx_queues)+return-EINVAL;++if(!peer_dev)+return-EINVAL;++peer_priv=netdev_priv(peer_dev);++/* to do: this may be failed */+if(!(peer_priv->_xdp_prog)&&(!veth_gro_requested(peer_dev))){+/* disable peer napi */+veth_napi_del(peer_dev);+}++veth_napi_del_tx(dev);++rcu_assign_pointer(priv->sq[qid].xsk.pool,NULL);+returnerr;+}++/* this is for setup xdp */+staticintveth_xsk_pool_setup(structnet_device*dev,structnetdev_bpf*xdp)+{+if(xdp->xsk.pool)+returnveth_xsk_pool_enable(dev,xdp->xsk.pool,xdp->xsk.queue_id);+else+returnveth_xsk_pool_disable(dev,xdp->xsk.queue_id);+}+staticintveth_xdp(structnet_device*dev,structnetdev_bpf*xdp){switch(xdp->command){caseXDP_SETUP_PROG:returnveth_xdp_set(dev,xdp->prog,xdp->extack);+caseXDP_SETUP_XSK_POOL:+returnveth_xsk_pool_setup(dev,xdp);default:return-EINVAL;}
in order to support native af_xdp for veth. we
need support for send queue for napi tx.
the upcoming patch will make use of it.
Signed-off-by: huangjie.albert <redacted>
---
drivers/net/veth.c | 29 +++++++++++++++++++++++++++++
1 file changed, 29 insertions(+)
@@ -56,6 +56,11 @@ struct veth_rq_stats {structu64_stats_syncsyncp;};+structveth_sq_stats{+structveth_statsvs;+structu64_stats_syncsyncp;+};+structveth_rq{structnapi_structxdp_napi;structnapi_struct__rcu*napi;/* points to xdp_napi when the latter is initialized */
@@ -69,11 +74,25 @@ struct veth_rq {structpage_pool*page_pool;};+structveth_sq{+structnapi_structxdp_napi;+structnet_device*dev;+structxdp_mem_infoxdp_mem;+structveth_sq_statsstats;+u32queue_index;+/* this is for xsk */+struct{+structxsk_buff_pool__rcu*pool;+u32last_cpu;+}xsk;+};+structveth_priv{structnet_device__rcu*peer;atomic64_tdropped;structbpf_prog*_xdp_prog;structveth_rq*rq;+structveth_sq*sq;unsignedintrequested_headroom;};
@@ -1495,6 +1514,15 @@ static int veth_alloc_queues(struct net_device *dev)u64_stats_init(&priv->rq[i].stats.syncp);}+priv->sq=kcalloc(dev->num_tx_queues,sizeof(*priv->sq),GFP_KERNEL);+if(!priv->sq)+return-ENOMEM;++for(i=0;i<dev->num_tx_queues;i++){+priv->sq[i].dev=dev;+u64_stats_init(&priv->sq[i].stats.syncp);+}+return0;}
@@ -1840,6 +1840,45 @@ static void veth_set_rx_headroom(struct net_device *dev, int new_hr)rcu_read_unlock();}+staticvoidveth_xsk_remote_trigger_napi(void*info)+{+structveth_sq*sq=info;++napi_schedule(&sq->xdp_napi);+}++staticintveth_xsk_wakeup(structnet_device*dev,u32qid,u32flag)+{+structveth_priv*priv;+structveth_sq*sq;+u32last_cpu,cur_cpu;++if(!netif_running(dev))+return-ENETDOWN;++if(qid>=dev->real_num_rx_queues)+return-EINVAL;++priv=netdev_priv(dev);+sq=&priv->sq[qid];++if(napi_if_scheduled_mark_missed(&sq->xdp_napi))+return0;++last_cpu=sq->xsk.last_cpu;+cur_cpu=get_cpu();++/* raise a napi */+if(last_cpu==cur_cpu){+napi_schedule(&sq->xdp_napi);+}else{+smp_call_function_single(last_cpu,veth_xsk_remote_trigger_napi,sq,true);+}++put_cpu();+return0;+}+staticintveth_xdp_set(structnet_device*dev,structbpf_prog*prog,structnetlink_ext_ack*extack){
this member is add for dummy dev to suppot zero copy
Signed-off-by: huangjie.albert <redacted>
---
include/linux/skbuff.h | 1 +
1 file changed, 1 insertion(+)
@@ -592,6 +592,7 @@ struct skb_shared_info {/* Intermediate layers must ensure that destructor_arg*remainsvaliduntilskbdestructor*/void*destructor_arg;+void*destructor_arg_xsk_pool;/* just for dummy device xsk zero copy *//* must be last field, see pskb_expand_head() */skb_frag_tfrags[MAX_SKB_FRAGS];
this type of xdp mem will be used for zero copy in
later patch
Signed-off-by: huangjie.albert <redacted>
---
include/net/xdp.h | 1 +
1 file changed, 1 insertion(+)
A typical topology is shown below:
veth<--------veth-peer
1 |
|2
|
bridge<------->eth0(such as mlnx5 NIC)
If you use af_xdp to send packets from veth to a physical NIC,
it needs to go through some software paths, so we can refer to
the implementation of kernel GSO. When af_xdp sends packets out
from veth, consider aggregating packets and send a large packet
from the veth virtual NIC to the physical NIC.
performance:(test weth libxdp lib)
AF_XDP without batch : 480 Kpps (with ksoftirqd 100% cpu)
AF_XDP with batch : 1.5 Mpps (with ksoftirqd 15% cpu)
With af_xdp batch, the libxdp user-space program reaches a bottleneck.
Therefore, the softirq did not reach the limit.
Signed-off-by: huangjie.albert <redacted>
---
drivers/net/veth.c | 264 ++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 249 insertions(+), 15 deletions(-)
@@ -650,6 +663,84 @@ static int veth_xdp_tx(struct veth_rq *rq, struct xdp_buff *xdp,return0;}+staticstructsk_buff*veth_build_gso_head_skb(structnet_device*dev,char*buff,u32tot_len,u32headroom,u32iph_len,u32th_len)+{+structsk_buff*skb=NULL;+interr=0;++skb=alloc_skb(tot_len,GFP_KERNEL);+if(unlikely(!skb))+returnNULL;++/* header room contains the eth header */+skb_reserve(skb,headroom-ETH_HLEN);++skb_put(skb,ETH_HLEN+iph_len+th_len);++skb_shinfo(skb)->gso_segs=0;++err=skb_store_bits(skb,0,buff,ETH_HLEN+iph_len+th_len);+if(unlikely(err)){+kfree_skb(skb);+returnNULL;+}++skb->protocol=eth_type_trans(skb,dev);+skb->network_header=skb->mac_header+ETH_HLEN;+skb->transport_header=skb->network_header+iph_len;+skb->ip_summed=CHECKSUM_PARTIAL;++returnskb;+}++staticinlineboolgso_segment_match(structveth_gso_tuple*gso_tuple,structiphdr*iph,structudphdr*udph)+{+if(gso_tuple->protocol==iph->protocol&&+gso_tuple->saddr==iph->saddr&&+gso_tuple->daddr==iph->daddr&&+gso_tuple->source==udph->source&&+gso_tuple->dest==udph->dest&&+gso_tuple->gso_size==ntohs(udph->len))+{+gso_tuple->gso_flush=false;+returntrue;+}else{+gso_tuple->gso_flush=true;+returnfalse;+}+}++staticinlinevoidgso_tuple_init(structveth_gso_tuple*gso_tuple,structiphdr*iph,structudphdr*udph)+{+gso_tuple->protocol=iph->protocol;+gso_tuple->saddr=iph->saddr;+gso_tuple->daddr=iph->daddr;+gso_tuple->source=udph->source;+gso_tuple->dest=udph->dest;+gso_tuple->gso_flush=false;+gso_tuple->gso_size=ntohs(udph->len);+gso_tuple->gso_segs=0;+}++/* only ipv4 udp support gso now */+staticinlineboolip_hdr_gso_check(unsignedchar*buff,u32len)+{+structiphdr*iph;++if(len<=(ETH_HLEN+sizeof(*iph)))+returnfalse;++iph=(structiphdr*)(buff+ETH_HLEN);++/*+*checkforipheaders,ifthedatasupportgso+*/+if(iph->ihl<5||iph->version!=4||len<(iph->ihl*4+ETH_HLEN)||iph->protocol!=IPPROTO_UDP)+returnfalse;++returntrue;+}+staticstructsk_buff*veth_build_skb(void*head,intheadroom,intlen,intbuflen){
@@ -698,45 +789,133 @@ static struct sk_buff *veth_build_skb_zerocopy(struct net_device *dev, struct xsintheadroom;u64addr;u32index;-addr=desc->addr;len=desc->len;buffer=xsk_buff_raw_get_data(pool,addr);ts=pool->unaligned?len:pool->chunk_size;-headroom=offset_in_page(buffer);-/* offset in umem pool buffer */addr=buffer-pool->addrs;-/* get the page of the desc */page=pool->umem->pgs[addr>>PAGE_SHIFT];-/* in order to avoid to get freed by kfree_skb */get_page(page);-hard_start=page_to_virt(page);-skb=veth_build_skb(hard_start,headroom,len,ts);seg_info=(structveth_seg_info*)kmalloc(struct_size(seg_info,desc,MAX_SKB_FRAGS),GFP_KERNEL);if(!seg_info){printk("here must to deal with\n");}-/* later we will support gso for this */index=skb_shinfo(skb)->gso_segs;seg_info->desc[index]=desc->addr;seg_info->segs=++index;-skb->truesize+=ts;skb->dev=dev;skb_shinfo(skb)->destructor_arg=(void*)(long)seg_info;skb_shinfo(skb)->destructor_arg_xsk_pool=(void*)(long)pool;skb->destructor=veth_xsk_destruct_skb;-/* set the mac header */skb->protocol=eth_type_trans(skb,dev);+/* to do, add skb to sock. may be there is no need to do for this+*refcount_add(ts,&xs->sk.sk_wmem_alloc);+*/+returnskb;+}++staticstructsk_buff*veth_build_skb_zerocopy_gso(structnet_device*dev,structxsk_buff_pool*pool,+structxdp_desc*desc,structveth_gso_tuple*gso_tuple,structsk_buff*prev_skb)+{+u32hr,len,ts,index,iph_len,th_len,data_offset,data_len,tot_len;+structveth_seg_info*seg_info;+void*buffer;+structudphdr*udph;+structiphdr*iph;+structsk_buff*skb;+structpage*page;+inthh_len=0;+u64addr;++addr=desc->addr;+len=desc->len;++/* l2 reserved len */+hh_len=LL_RESERVED_SPACE(dev);+hr=max(NET_SKB_PAD,L1_CACHE_ALIGN(hh_len));++/* data points to eth header */+buffer=(unsignedchar*)xsk_buff_raw_get_data(pool,addr);++iph=(structiphdr*)(buffer+ETH_HLEN);+iph_len=iph->ihl*4;++udph=(structudphdr*)(buffer+ETH_HLEN+iph_len);+th_len=sizeof(structudphdr);++if(gso_tuple->gso_flush)+gso_tuple_init(gso_tuple,iph,udph);++ts=pool->unaligned?len:pool->chunk_size;++data_offset=offset_in_page(buffer)+ETH_HLEN+iph_len+th_len;+data_len=len-(ETH_HLEN+iph_len+th_len);++/* head is null or this is a new 5 tuple */+if(NULL==prev_skb||!gso_segment_match(gso_tuple,iph,udph)){+tot_len=hr+iph_len+th_len;+skb=veth_build_gso_head_skb(dev,buffer,tot_len,hr,iph_len,th_len);+if(!skb){+/* to do: handle here for skb */+returnNULL;+}++/* store information for gso */+seg_info=(structveth_seg_info*)kmalloc(struct_size(seg_info,desc,MAX_SKB_FRAGS),GFP_KERNEL);+if(!seg_info){+/* to do */+kfree_skb(skb);+returnNULL;+}+}else{+skb=prev_skb;+skb_shinfo(skb)->gso_type=SKB_GSO_UDP_L4|SKB_GSO_PARTIAL;+skb_shinfo(skb)->gso_size=data_len;+skb->ip_summed=CHECKSUM_PARTIAL;++/* max segment is MAX_SKB_FRAGS */+if(skb_shinfo(skb)->gso_segs>=MAX_SKB_FRAGS-1){+gso_tuple->gso_flush=true;+}+seg_info=(structveth_seg_info*)skb_shinfo(skb)->destructor_arg;+}++/* offset in umem pool buffer */+addr=buffer-pool->addrs;++/* get the page of the desc */+page=pool->umem->pgs[addr>>PAGE_SHIFT];++/* in order to avoid to get freed by kfree_skb */+get_page(page);++/* desc.data can not hold in two */+skb_fill_page_desc(skb,skb_shinfo(skb)->gso_segs,page,data_offset,data_len);++skb->len+=data_len;+skb->data_len+=data_len;+skb->truesize+=ts;+skb->dev=dev;++/* later we will support gso for this */+index=skb_shinfo(skb)->gso_segs;+seg_info->desc[index]=desc->addr;+seg_info->segs=++index;+skb_shinfo(skb)->gso_segs++;++skb_shinfo(skb)->destructor_arg=(void*)(long)seg_info;+skb_shinfo(skb)->destructor_arg_xsk_pool=(void*)(long)pool;+skb->destructor=veth_xsk_destruct_skb;/* to do, add skb to sock. may be there is no need to do for this*refcount_add(ts,&xs->sk.sk_wmem_alloc);
@@ -1200,6 +1412,8 @@ static int veth_xsk_tx_xmit(struct veth_sq *sq, struct xsk_buff_pool *xsk_pool,/* todo: queue index must set before this */peer_rq=&peer_priv->rq[sq->queue_index];+memset(&gso_tuple,0,sizeof(gso_tuple));+/* set xsk wake up flag, to do: where to disable */if(xsk_uses_need_wakeup(xsk_pool))xsk_set_tx_need_wakeup(xsk_pool);
@@ -1279,12 +1493,26 @@ static int veth_xsk_tx_xmit(struct veth_sq *sq, struct xsk_buff_pool *xsk_pool,/* no need to copy address for af+xdp */p_frame=veth_xdp_rcv_one(peer_rq,p_frame,&bq,&peer_stats);if(p_frame){-skb=veth_build_skb_zerocopy(peer_dev,xsk_pool,&desc);-if(skb){+skb=veth_build_skb_zerocopy(peer_dev,xsk_pool,&desc,&gso_tuple,prev_skb);+if(!gso_tuple.gso_enable){napi_gro_receive(&peer_rq->xdp_napi,skb);skb=NULL;}else{-xsk_tx_completed_addr(xsk_pool,desc.addr);+if(prev_skb&&gso_tuple.gso_flush){+veth_skb_gso_check_update(prev_skb);+napi_gro_receive(&peer_rq->xdp_napi,prev_skb);++if(prev_skb==skb){+skb=NULL;+prev_skb=NULL;+}else{+prev_skb=skb;+}+}elseif(NULL==skb){+xsk_tx_completed_addr(xsk_pool,desc.addr);+}else{+prev_skb=skb;+}}}}else{
The following conditions need to be satisfied to achieve zero-copy:
1. The tx desc has enough space to store the xdp_frame and skb_share_info.
2. The memory address pointed to by the tx desc is within a page.
test zero copy with libxdp
Performance:
|MSS (bytes) | Packet rate (PPS)
AF_XDP | 1300 | 480k
AF_XDP with zero copy| 1300 | 540K
signed-off-by: huangjie.albert <redacted>
---
drivers/net/veth.c | 207 ++++++++++++++++++++++++++++++++++++++-------
1 file changed, 178 insertions(+), 29 deletions(-)
@@ -645,6 +650,100 @@ static int veth_xdp_tx(struct veth_rq *rq, struct xdp_buff *xdp,return0;}+staticstructsk_buff*veth_build_skb(void*head,intheadroom,intlen,+intbuflen)+{+structsk_buff*skb;++skb=build_skb(head,buflen);+if(!skb)+returnNULL;++skb_reserve(skb,headroom);+skb_put(skb,len);++returnskb;+}++staticvoidveth_xsk_destruct_skb(structsk_buff*skb)+{+structveth_seg_info*seg_info=(structveth_seg_info*)skb_shinfo(skb)->destructor_arg;+structxsk_buff_pool*pool=(structxsk_buff_pool*)skb_shinfo(skb)->destructor_arg_xsk_pool;+unsignedlongflags;+u32index=0;+u64addr;++/* release cq */+spin_lock_irqsave(&pool->cq_lock,flags);+for(index=0;index<seg_info->segs;index++){+addr=(u64)(long)seg_info->desc[index];+xsk_tx_completed_addr(pool,addr);+}+spin_unlock_irqrestore(&pool->cq_lock,flags);++kfree(seg_info);+skb_shinfo(skb)->destructor_arg=NULL;+skb_shinfo(skb)->destructor_arg_xsk_pool=NULL;+}++staticstructsk_buff*veth_build_skb_zerocopy(structnet_device*dev,structxsk_buff_pool*pool,+structxdp_desc*desc)+{+structveth_seg_info*seg_info;+structsk_buff*skb;+structpage*page;+void*hard_start;+u32len,ts;+void*buffer;+intheadroom;+u64addr;+u32index;++addr=desc->addr;+len=desc->len;+buffer=xsk_buff_raw_get_data(pool,addr);+ts=pool->unaligned?len:pool->chunk_size;++headroom=offset_in_page(buffer);++/* offset in umem pool buffer */+addr=buffer-pool->addrs;++/* get the page of the desc */+page=pool->umem->pgs[addr>>PAGE_SHIFT];++/* in order to avoid to get freed by kfree_skb */+get_page(page);++hard_start=page_to_virt(page);++skb=veth_build_skb(hard_start,headroom,len,ts);+seg_info=(structveth_seg_info*)kmalloc(struct_size(seg_info,desc,MAX_SKB_FRAGS),GFP_KERNEL);+if(!seg_info)+{+printk("here must to deal with\n");+}++/* later we will support gso for this */+index=skb_shinfo(skb)->gso_segs;+seg_info->desc[index]=desc->addr;+seg_info->segs=++index;++skb->truesize+=ts;+skb->dev=dev;+skb_shinfo(skb)->destructor_arg=(void*)(long)seg_info;+skb_shinfo(skb)->destructor_arg_xsk_pool=(void*)(long)pool;+skb->destructor=veth_xsk_destruct_skb;++/* set the mac header */+skb->protocol=eth_type_trans(skb,dev);++/* to do, add skb to sock. may be there is no need to do for this+*refcount_add(ts,&xs->sk.sk_wmem_alloc);+*/+returnskb;+}+staticstructxdp_frame*veth_xdp_rcv_one(structveth_rq*rq,structxdp_frame*frame,structveth_xdp_tx_bq*bq,
@@ -1063,6 +1162,20 @@ static int veth_poll(struct napi_struct *napi, int budget)returndone;}+/* if buffer contain in a page */+staticinlineboolbuffer_in_page(void*buffer,u32len)+{+u32offset;++offset=offset_in_page(buffer);++if(PAGE_SIZE-offset>=len){+returntrue;+}else{+returnfalse;+}+}+staticintveth_xsk_tx_xmit(structveth_sq*sq,structxsk_buff_pool*xsk_pool,intbudget){structveth_priv*priv,*peer_priv;
@@ -1102,12 +1218,6 @@ static int veth_xsk_tx_xmit(struct veth_sq *sq, struct xsk_buff_pool *xsk_pool,break;}-/*-*Getaxmitaddr-*desc.addrisaoffset,soweshouldtoconverttorealvirtualaddress-*/-addr=xsk_buff_raw_get_data(xsk_pool,desc.addr);-/* can not hold all data in a page */truesize=SKB_DATA_ALIGN(sizeof(structskb_shared_info))+desc.len+sizeof(structxdp_frame);if(truesize>PAGE_SIZE){
@@ -1116,16 +1226,39 @@ static int veth_xsk_tx_xmit(struct veth_sq *sq, struct xsk_buff_pool *xsk_pool,continue;}-page=dev_alloc_page();-if(!page){-/*-*error,releasexdpframeandincreasedrops-*/-xsk_tx_completed_addr(xsk_pool,desc.addr);-stats.xdp_drops++;-break;+/*+*Getaxmitaddr+*desc.addrisaoffset,soweshouldtoconverttorealvirtualaddress+*/+addr=xsk_buff_raw_get_data(xsk_pool,desc.addr);++/*+*inordertosupportzerocopy,headroommusthaveenoughspacetoholdxdp_frame+*/+if(zc&&(xsk_headroom<sizeof(structxdp_frame)))+zc=false;++/*+*ifdescnotcontaininapage,alsodonotsupportzerocopy+*/+if(!buffer_in_page(addr,desc.len))+zc=false;++if(zc){+/* headroom is reserved for xdp_frame */+new_addr=addr-sizeof(structxdp_frame);+}else{+page=dev_alloc_page();+if(!page){+/*+*error,releasexdpframeandincreasedrops+*/+xsk_tx_completed_addr(xsk_pool,desc.addr);+stats.xdp_drops++;+break;+}+new_addr=page_to_virt(page);}-new_addr=page_to_virt(page);p_frame=new_addr;new_addr+=sizeof(structxdp_frame);
@@ -1137,19 +1270,37 @@ static int veth_xsk_tx_xmit(struct veth_sq *sq, struct xsk_buff_pool *xsk_pool,*/p_frame->headroom=0;p_frame->metasize=0;-p_frame->frame_sz=PAGE_SIZE;p_frame->flags=0;-p_frame->mem.type=MEM_TYPE_PAGE_SHARED;-memcpy(p_frame->data,addr,p_frame->len);-xsk_tx_completed_addr(xsk_pool,desc.addr);--/* if peer have xdp prog, if it has ,just send to peer */-p_frame=veth_xdp_rcv_one(peer_rq,p_frame,&bq,&peer_stats);-/* if no xdp with this queue, convert to skb to xmit*/-if(p_frame){-xdpf=p_frame;-veth_xdp_rcv_bulk_skb(peer_rq,&xdpf,1,&bq,&peer_stats);-p_frame=NULL;++if(zc){+p_frame->frame_sz=xsk_pool->frame_len;+/* to do: if there is a xdp, how to recycle the tx desc */+p_frame->mem.type=MEM_TYPE_XSK_BUFF_POOL_TX;+/* no need to copy address for af+xdp */+p_frame=veth_xdp_rcv_one(peer_rq,p_frame,&bq,&peer_stats);+if(p_frame){+skb=veth_build_skb_zerocopy(peer_dev,xsk_pool,&desc);+if(skb){+napi_gro_receive(&peer_rq->xdp_napi,skb);+skb=NULL;+}else{+xsk_tx_completed_addr(xsk_pool,desc.addr);+}+}+}else{+p_frame->frame_sz=PAGE_SIZE;+p_frame->mem.type=MEM_TYPE_PAGE_SHARED;+memcpy(p_frame->data,addr,p_frame->len);+xsk_tx_completed_addr(xsk_pool,desc.addr);++/* if peer have xdp prog, if it has ,just send to peer */+p_frame=veth_xdp_rcv_one(peer_rq,p_frame,&bq,&peer_stats);+/* if no xdp with this queue, convert to skb to xmit*/+if(p_frame){+xdpf=p_frame;+veth_xdp_rcv_bulk_skb(peer_rq,&xdpf,1,&bq,&peer_stats);+p_frame=NULL;+}}stats.xdp_bytes+=desc.len;
@@ -1163,8 +1314,6 @@ static int veth_xsk_tx_xmit(struct veth_sq *sq, struct xsk_buff_pool *xsk_pool,xsk_tx_release(xsk_pool);}--/* just for peer rq */if(peer_stats.xdp_tx>0)veth_xdp_flush(peer_rq,&bq);
From: Paolo Abeni <pabeni@redhat.com> Date: 2023-08-03 14:21:23
On Thu, 2023-08-03 at 22:04 +0800, huangjie.albert wrote:
AF_XDP is a kernel bypass technology that can greatly improve performance.
However, for virtual devices like veth, even with the use of AF_XDP sockets,
there are still many additional software paths that consume CPU resources.
This patch series focuses on optimizing the performance of AF_XDP sockets
for veth virtual devices. Patches 1 to 4 mainly involve preparatory work.
Patch 5 introduces tx queue and tx napi for packet transmission, while
patch 9 primarily implements zero-copy, and patch 10 adds support for
batch sending of IPv4 UDP packets. These optimizations significantly reduce
the software path and support checksum offload.
I tested those feature with
A typical topology is shown below:
veth<-->veth-peer veth1-peer<--->veth1
1 | | 7
|2 6|
| |
bridge<------->eth0(mlnx5)- switch -eth1(mlnx5)<--->bridge1
3 4 5
(machine1) (machine2)
AF_XDP socket is attach to veth and veth1. and send packets to physical NIC(eth0)
veth:(172.17.0.2/24)
bridge:(172.17.0.1/24)
eth0:(192.168.156.66/24)
eth1(172.17.0.2/24)
bridge1:(172.17.0.1/24)
eth0:(192.168.156.88/24)
after set default route��?snat��?dnat. we can have a tests
to get the performance results.
packets send from veth to veth1:
af_xdp test tool:
link:https://github.com/cclinuxer/libxudp
send:(veth)
./objs/xudpperf send --dst 192.168.156.88:6002 -l 1300
recv:(veth1)
./objs/xudpperf recv --src 172.17.0.2:6002
udp test tool:iperf3
send:(veth)
iperf3 -c 192.168.156.88 -p 6002 -l 1300 -b 60G -u
Should be: '-b 0' otherwise you will experience additional overhead.
And you would likely pin processes and irqs to ensure BH and US run on
different cores of the same numa node.
Cheers,
Paolo
AF_XDP is a kernel bypass technology that can greatly improve performance.
However, for virtual devices like veth, even with the use of AF_XDP sockets,
there are still many additional software paths that consume CPU resources.
This patch series focuses on optimizing the performance of AF_XDP sockets
for veth virtual devices. Patches 1 to 4 mainly involve preparatory work.
Patch 5 introduces tx queue and tx napi for packet transmission, while
patch 9 primarily implements zero-copy, and patch 10 adds support for
batch sending of IPv4 UDP packets. These optimizations significantly reduce
the software path and support checksum offload.
I tested those feature with
A typical topology is shown below:
veth<-->veth-peer veth1-peer<--->veth1
1 | | 7
|2 6|
| |
bridge<------->eth0(mlnx5)- switch -eth1(mlnx5)<--->bridge1
3 4 5
(machine1) (machine2)
AF_XDP socket is attach to veth and veth1. and send packets to physical NIC(eth0)
veth:(172.17.0.2/24)
bridge:(172.17.0.1/24)
eth0:(192.168.156.66/24)
eth1(172.17.0.2/24)
bridge1:(172.17.0.1/24)
eth0:(192.168.156.88/24)
after set default route、snat、dnat. we can have a tests
to get the performance results.
packets send from veth to veth1:
af_xdp test tool:
link:https://github.com/cclinuxer/libxudp
send:(veth)
./objs/xudpperf send --dst 192.168.156.88:6002 -l 1300
recv:(veth1)
./objs/xudpperf recv --src 172.17.0.2:6002
udp test tool:iperf3
send:(veth)
iperf3 -c 192.168.156.88 -p 6002 -l 1300 -b 60G -u
recv:(veth1)
iperf3 -s -p 6002
performance:
performance:(test weth libxdp lib)
UDP : 250 Kpps (with 100% cpu)
AF_XDP no zerocopy + no batch : 480 Kpps (with ksoftirqd 100% cpu)
AF_XDP with zerocopy + no batch : 540 Kpps (with ksoftirqd 100% cpu)
AF_XDP with batch + zerocopy : 1.5 Mpps (with ksoftirqd 15% cpu)
With af_xdp batch, the libxdp user-space program reaches a bottleneck.
Therefore, the softirq did not reach the limit.
This is just an RFC patch series, and some code details still need
further consideration. Please review this proposal.
I find this performance work interesting as we have customer requests
(via Maryam (cc)) to improve AF_XDP performance both native and on veth.
Our benchmark is stored at:
https://github.com/maryamtahhan/veth-benchmark
Great to see other companies also interested in this area.
--Jesper
thanks!
huangjie.albert (10):
veth: Implement ethtool's get_ringparam() callback
xsk: add dma_check_skip for skipping dma check
veth: add support for send queue
xsk: add xsk_tx_completed_addr function
veth: use send queue tx napi to xmit xsk tx desc
veth: add ndo_xsk_wakeup callback for veth
sk_buff: add destructor_arg_xsk_pool for zero copy
xdp: add xdp_mem_type MEM_TYPE_XSK_BUFF_POOL_TX
veth: support zero copy for af xdp
veth: af_xdp tx batch support for ipv4 udp
drivers/net/veth.c | 729 +++++++++++++++++++++++++++++++++++-
include/linux/skbuff.h | 1 +
include/net/xdp.h | 1 +
include/net/xdp_sock_drv.h | 1 +
include/net/xsk_buff_pool.h | 1 +
net/xdp/xsk.c | 6 +
net/xdp/xsk_buff_pool.c | 3 +-
net/xdp/xsk_queue.h | 11 +
8 files changed, 751 insertions(+), 2 deletions(-)
On Thu, 2023-08-03 at 22:04 +0800, huangjie.albert wrote:
quoted
AF_XDP is a kernel bypass technology that can greatly improve performance.
However, for virtual devices like veth, even with the use of AF_XDP sockets,
there are still many additional software paths that consume CPU resources.
This patch series focuses on optimizing the performance of AF_XDP sockets
for veth virtual devices. Patches 1 to 4 mainly involve preparatory work.
Patch 5 introduces tx queue and tx napi for packet transmission, while
patch 9 primarily implements zero-copy, and patch 10 adds support for
batch sending of IPv4 UDP packets. These optimizations significantly reduce
the software path and support checksum offload.
I tested those feature with
A typical topology is shown below:
veth<-->veth-peer veth1-peer<--->veth1
1 | | 7
|2 6|
| |
bridge<------->eth0(mlnx5)- switch -eth1(mlnx5)<--->bridge1
3 4 5
(machine1) (machine2)
AF_XDP socket is attach to veth and veth1. and send packets to physical NIC(eth0)
veth:(172.17.0.2/24)
bridge:(172.17.0.1/24)
eth0:(192.168.156.66/24)
eth1(172.17.0.2/24)
bridge1:(172.17.0.1/24)
eth0:(192.168.156.88/24)
after set default route . snat . dnat. we can have a tests
to get the performance results.
packets send from veth to veth1:
af_xdp test tool:
link:https://github.com/cclinuxer/libxudp
send:(veth)
./objs/xudpperf send --dst 192.168.156.88:6002 -l 1300
recv:(veth1)
./objs/xudpperf recv --src 172.17.0.2:6002
udp test tool:iperf3
send:(veth)
iperf3 -c 192.168.156.88 -p 6002 -l 1300 -b 60G -u
Should be: '-b 0' otherwise you will experience additional overhead.
with -b 0:
performance:
performance:(test weth libxdp lib)
UDP : 320 Kpps (with 100% cpu)
AF_XDP no zerocopy + no batch : 480 Kpps (with ksoftirqd 100% cpu)
AF_XDP with zerocopy + no batch : 540 Kpps (with ksoftirqd 100% cpu)
AF_XDP with batch + zerocopy : 1.5 Mpps (with ksoftirqd 15% cpu)
thanks.
And you would likely pin processes and irqs to ensure BH and US run on
different cores of the same numa node.
Cheers,
Paolo
From: Simon Horman <horms@kernel.org> Date: 2023-08-04 20:43:02
On Thu, Aug 03, 2023 at 10:04:28PM +0800, huangjie.albert wrote:
for the virtual net device such as veth, there is
no need to do dma check if we support zero copy.
add this flag after unaligned. beacause there are 4 bytes hole
nit: beacause there are 4 bytes hole
-> Because there is a 4 byte hole.
Please try to use reverse xmas tree ordering - longest line to shortest -
for local variable declarations in new Networking code.
https://github.com/ecree-solarflare/xmastree is your friend here.
Sparse seems a bit unhappy about this.
.../veth.c:1081:18: warning: incorrect type in assignment (different address spaces)
.../veth.c:1081:18: expected struct net_device *peer_dev
.../veth.c:1081:18: got struct net_device [noderef] __rcu *peer
Looking over existing code in this file, perhaps this is appropriate:
peer_dev = rtnl_dereference(priv->peer);
Likewise in a few other places in this patch.
...
@@ -1840,6 +1840,45 @@ static void veth_set_rx_headroom(struct net_device *dev, int new_hr)rcu_read_unlock();}+staticvoidveth_xsk_remote_trigger_napi(void*info)+{+structveth_sq*sq=info;++napi_schedule(&sq->xdp_napi);+}++staticintveth_xsk_wakeup(structnet_device*dev,u32qid,u32flag)+{+structveth_priv*priv;+structveth_sq*sq;+u32last_cpu,cur_cpu;++if(!netif_running(dev))+return-ENETDOWN;++if(qid>=dev->real_num_rx_queues)+return-EINVAL;++priv=netdev_priv(dev);+sq=&priv->sq[qid];++if(napi_if_scheduled_mark_missed(&sq->xdp_napi))+return0;++last_cpu=sq->xsk.last_cpu;+cur_cpu=get_cpu();++/* raise a napi */+if(last_cpu==cur_cpu){+napi_schedule(&sq->xdp_napi);+}else{+smp_call_function_single(last_cpu,veth_xsk_remote_trigger_napi,sq,true);+}
nit: no need for braces in the above.
if (last_cpu == cur_cpu)
napi_schedule(&sq->xdp_napi);
else
smp_call_function_single(last_cpu, veth_xsk_remote_trigger_napi, sq, true);
...
From: Simon Horman <horms@kernel.org> Date: 2023-08-04 21:05:48
On Thu, Aug 03, 2023 at 10:04:35PM +0800, huangjie.albert wrote:
...
+static struct sk_buff *veth_build_skb_zerocopy(struct net_device *dev, struct xsk_buff_pool *pool,
+ struct xdp_desc *desc)
+{
+ struct veth_seg_info *seg_info;
+ struct sk_buff *skb;
+ struct page *page;
+ void *hard_start;
+ u32 len, ts;
+ void *buffer;
+ int headroom;
+ u64 addr;
+ u32 index;
+
+ addr = desc->addr;
+ len = desc->len;
+ buffer = xsk_buff_raw_get_data(pool, addr);
+ ts = pool->unaligned ? len : pool->chunk_size;
+
+ headroom = offset_in_page(buffer);
+
+ /* offset in umem pool buffer */
+ addr = buffer - pool->addrs;
+
+ /* get the page of the desc */
+ page = pool->umem->pgs[addr >> PAGE_SHIFT];
+
+ /* in order to avoid to get freed by kfree_skb */
+ get_page(page);
+
+ hard_start = page_to_virt(page);
+
+ skb = veth_build_skb(hard_start, headroom, len, ts);
+ seg_info = (struct veth_seg_info *)kmalloc(struct_size(seg_info, desc, MAX_SKB_FRAGS), GFP_KERNEL);
There is no need to explicitly case the return value of kmalloc,
as it returns void *.
seg_info = kmalloc(struct_size(seg_info, desc, MAX_SKB_FRAGS),
GFP_KERNEL);
...
Likewise, here.
As flagged by Sparse.
.../veth.c:721:29: warning: incorrect type in assignment (different base types)
.../veth.c:721:29: expected restricted __be16 [usertype] gso_size
.../veth.c:721:29: got unsigned short [usertype]
.../veth.c:703:26: warning: restricted __be16 degrades to integer