[net-next,2/2] tuntap: XDP transmission
diff mbox series

Message ID 1514541604-12728-3-git-send-email-jasowang@redhat.com
State New, archived
Headers show
Series
  • XDP transmission for tuntap
Related show

Commit Message

Jason Wang Dec. 29, 2017, 10 a.m. UTC
This patch implements XDP transmission for TAP. Since we can't create
new queues for TAP during XDP set, exist ptr_ring was reused for
queuing XDP buffers. To differ xdp_buff from sk_buff, TUN_XDP_FLAG
(0x1ULL) was encoded into lowest bit of xpd_buff pointer during
ptr_ring_produce, and was decoded during consuming. XDP metadata was
stored in the headroom of the packet which should work in most of
cases since driver usually reserve enough headroom. Very minor changes
were done for vhost_net: it just need to peek the length depends on
the type of pointer.

Tests was done on two Intel E5-2630 2.40GHz machines connected back to
back through two 82599ES. Traffic were generated through MoonGen and
testpmd(rxonly) in guest reports 2.97Mpps when xdp_redirect_map is
doing redirection from ixgbe to TAP.

Cc: Jesper Dangaard Brouer <brouer@redhat.com>
Signed-off-by: Jason Wang <jasowang@redhat.com>
---
 drivers/net/tun.c      | 205 ++++++++++++++++++++++++++++++++++++++++---------
 drivers/vhost/net.c    |  13 +++-
 include/linux/if_tun.h |  17 ++++
 3 files changed, 197 insertions(+), 38 deletions(-)

Comments

Jesper Dangaard Brouer Dec. 29, 2017, 12:32 p.m. UTC | #1
On Fri, 29 Dec 2017 18:00:04 +0800
Jason Wang <jasowang@redhat.com> wrote:

> This patch implements XDP transmission for TAP. Since we can't create
> new queues for TAP during XDP set, exist ptr_ring was reused for
> queuing XDP buffers. To differ xdp_buff from sk_buff, TUN_XDP_FLAG
> (0x1ULL) was encoded into lowest bit of xpd_buff pointer during
> ptr_ring_produce, and was decoded during consuming. XDP metadata was
> stored in the headroom of the packet which should work in most of
> cases since driver usually reserve enough headroom. Very minor changes
> were done for vhost_net: it just need to peek the length depends on
> the type of pointer.
> 
> Tests was done on two Intel E5-2630 2.40GHz machines connected back to
> back through two 82599ES. Traffic were generated through MoonGen and
> testpmd(rxonly) in guest reports 2.97Mpps when xdp_redirect_map is
> doing redirection from ixgbe to TAP.

IMHO a performance measurement without something to compare against is
useless.  What was the performance before?


> Cc: Jesper Dangaard Brouer <brouer@redhat.com>
> Signed-off-by: Jason Wang <jasowang@redhat.com>
> ---
>  drivers/net/tun.c      | 205 ++++++++++++++++++++++++++++++++++++++++---------
>  drivers/vhost/net.c    |  13 +++-
>  include/linux/if_tun.h |  17 ++++
>  3 files changed, 197 insertions(+), 38 deletions(-)
> 
> diff --git a/drivers/net/tun.c b/drivers/net/tun.c
> index 2c89efe..be6d993 100644
> --- a/drivers/net/tun.c
> +++ b/drivers/net/tun.c
> @@ -240,6 +240,24 @@ struct tun_struct {
>  	struct tun_steering_prog __rcu *steering_prog;
>  };
>  
> +bool tun_is_xdp_buff(void *ptr)
> +{
> +	return (unsigned long)ptr & TUN_XDP_FLAG;
> +}
> +EXPORT_SYMBOL(tun_is_xdp_buff);
> +
> +void *tun_xdp_to_ptr(void *ptr)
> +{
> +	return (void *)((unsigned long)ptr | TUN_XDP_FLAG);
> +}
> +EXPORT_SYMBOL(tun_xdp_to_ptr);
> +
> +void *tun_ptr_to_xdp(void *ptr)
> +{
> +	return (void *)((unsigned long)ptr & ~TUN_XDP_FLAG);
> +}
> +EXPORT_SYMBOL(tun_ptr_to_xdp);
> +
>  static int tun_napi_receive(struct napi_struct *napi, int budget)
>  {
>  	struct tun_file *tfile = container_of(napi, struct tun_file, napi);
> @@ -630,12 +648,25 @@ static struct tun_struct *tun_enable_queue(struct tun_file *tfile)
>  	return tun;
>  }
>  
> +static void tun_ptr_free(void *ptr)
> +{
> +	if (!ptr)
> +		return;
> +	if (tun_is_xdp_buff(ptr)) {
> +		struct xdp_buff *xdp = tun_ptr_to_xdp(ptr);
> +
> +		put_page(virt_to_head_page(xdp->data));

(Yet another XDP-free call point, I need to convert to use an accessor
later to transition driver into an xdp_buff return API)

> +	} else {
> +		__skb_array_destroy_skb(ptr);
> +	}
> +}
> +
>  static void tun_queue_purge(struct tun_file *tfile)
>  {
> -	struct sk_buff *skb;
> +	void *ptr;
>  
> -	while ((skb = ptr_ring_consume(&tfile->tx_ring)) != NULL)
> -		kfree_skb(skb);
> +	while ((ptr = ptr_ring_consume(&tfile->tx_ring)) != NULL)
> +		tun_ptr_free(ptr);
>  
>  	skb_queue_purge(&tfile->sk.sk_write_queue);
>  	skb_queue_purge(&tfile->sk.sk_error_queue);
> @@ -688,8 +719,7 @@ static void __tun_detach(struct tun_file *tfile, bool clean)
>  				unregister_netdevice(tun->dev);
>  		}
>  		if (tun)
> -			ptr_ring_cleanup(&tfile->tx_ring,
> -					 __skb_array_destroy_skb);
> +			ptr_ring_cleanup(&tfile->tx_ring, tun_ptr_free);
>  		sock_put(&tfile->sk);
>  	}
>  }
> @@ -1201,6 +1231,54 @@ static const struct net_device_ops tun_netdev_ops = {
>  	.ndo_get_stats64	= tun_net_get_stats64,
>  };
>  
> +static int tun_xdp_xmit(struct net_device *dev, struct xdp_buff *xdp)
> +{
> +	struct tun_struct *tun = netdev_priv(dev);
> +	struct xdp_buff *buff = xdp->data_hard_start;
> +	int headroom = xdp->data - xdp->data_hard_start;
> +	struct tun_file *tfile;
> +	u32 numqueues;
> +	int ret = 0;
> +
> +	/* Assure headroom is available and buff is properly aligned */
> +	if (unlikely(headroom < sizeof(*xdp) || tun_is_xdp_buff(xdp)))
> +		return -ENOSPC;
> +
> +	*buff = *xdp;
> +
> +	rcu_read_lock();
> +
> +	numqueues = READ_ONCE(tun->numqueues);
> +	if (!numqueues) {
> +		ret = -ENOSPC;
> +		goto out;
> +	}
> +	tfile = rcu_dereference(tun->tfiles[smp_processor_id() %
> +					    numqueues]);

Several concurrent CPUs can get the same 'tfile'.

> +	/* Encode the XDP flag into lowest bit for consumer to differ
> +	 * XDP buffer from sk_buff.
> +	 */
> +	if (ptr_ring_produce(&tfile->tx_ring, tun_xdp_to_ptr(buff))) {
> +		this_cpu_inc(tun->pcpu_stats->tx_dropped);
> +		ret = -ENOSPC;
> +	}

The ptr_ring_produce() will take a lock per packet, limiting the
performance.  (Again a case where I would have liked a bulk API for
ndo_xdp_xmit()).


> +
> +out:
> +	rcu_read_unlock();
> +	return ret;
> +}
> +
> +static void tun_xdp_flush(struct net_device *dev)
> +{
> +	struct tun_struct *tun = netdev_priv(dev);
> +	struct tun_file *tfile = tun->tfiles[0];
> +
> +	/* Notify and wake up reader process */
> +	if (tfile->flags & TUN_FASYNC)
> +		kill_fasync(&tfile->fasync, SIGIO, POLL_IN);
> +	tfile->socket.sk->sk_data_ready(tfile->socket.sk);
> +}
> +
>  static const struct net_device_ops tap_netdev_ops = {
>  	.ndo_uninit		= tun_net_uninit,
>  	.ndo_open		= tun_net_open,
> @@ -1218,6 +1296,8 @@ static const struct net_device_ops tap_netdev_ops = {
>  	.ndo_set_rx_headroom	= tun_set_headroom,
>  	.ndo_get_stats64	= tun_net_get_stats64,
>  	.ndo_bpf		= tun_xdp,
> +	.ndo_xdp_xmit		= tun_xdp_xmit,
> +	.ndo_xdp_flush		= tun_xdp_flush,
>  };
>  
>  static void tun_flow_init(struct tun_struct *tun)
> @@ -1841,6 +1921,40 @@ static ssize_t tun_chr_write_iter(struct kiocb *iocb, struct iov_iter *from)
>  	return result;
>  }
>  
> +static ssize_t tun_put_user_xdp(struct tun_struct *tun,
> +				struct tun_file *tfile,
> +				struct xdp_buff *xdp,
> +				struct iov_iter *iter)
> +{
> +	int vnet_hdr_sz = 0;
> +	size_t size = xdp->data_end - xdp->data;
> +	struct tun_pcpu_stats *stats;
> +	size_t ret;
> +
> +	if (tun->flags & IFF_VNET_HDR) {
> +		struct virtio_net_hdr gso = { 0 };
> +
> +		vnet_hdr_sz = READ_ONCE(tun->vnet_hdr_sz);
> +		if (unlikely(iov_iter_count(iter) < vnet_hdr_sz))
> +			return -EINVAL;
> +		if (unlikely(copy_to_iter(&gso, sizeof(gso), iter) !=
> +			     sizeof(gso)))
> +			return -EFAULT;
> +		iov_iter_advance(iter, vnet_hdr_sz - sizeof(gso));
> +	}
> +
> +	ret = copy_to_iter(xdp->data, size, iter) + vnet_hdr_sz;
> +
> +	stats = get_cpu_ptr(tun->pcpu_stats);
> +	u64_stats_update_begin(&stats->syncp);
> +	stats->tx_packets++;
> +	stats->tx_bytes += ret;
> +	u64_stats_update_end(&stats->syncp);
> +	put_cpu_ptr(tun->pcpu_stats);
> +
> +	return ret;
> +}
> +
>  /* Put packet to the user space buffer */
>  static ssize_t tun_put_user(struct tun_struct *tun,
>  			    struct tun_file *tfile,
[...]
>  			error = -ERESTARTSYS;
> @@ -1977,36 +2090,42 @@ static struct sk_buff *tun_ring_recv(struct tun_file *tfile, int noblock,
>  
>  out:
>  	*err = error;
> -	return skb;
> +	return ptr;
>  }
>  
>  static ssize_t tun_do_read(struct tun_struct *tun, struct tun_file *tfile,
>  			   struct iov_iter *to,
> -			   int noblock, struct sk_buff *skb)
> +			   int noblock, void *ptr)
>  {
>  	ssize_t ret;
>  	int err;
>  
>  	tun_debug(KERN_INFO, tun, "tun_do_read\n");
>  
> -	if (!iov_iter_count(to)) {
> -		if (skb)
> -			kfree_skb(skb);
> -		return 0;
> -	}
> +	if (!iov_iter_count(to))
> +		tun_ptr_free(ptr);
>  
> -	if (!skb) {
> +	if (!ptr) {
>  		/* Read frames from ring */
> -		skb = tun_ring_recv(tfile, noblock, &err);
> -		if (!skb)
> +		ptr = tun_ring_recv(tfile, noblock, &err);
> +		if (!ptr)
>  			return err;
>  	}
>  
> -	ret = tun_put_user(tun, tfile, skb, to);
> -	if (unlikely(ret < 0))
> -		kfree_skb(skb);
> -	else
> -		consume_skb(skb);
> +	if (tun_is_xdp_buff(ptr)) {
> +		struct xdp_buff *xdp = tun_ptr_to_xdp(ptr);
> +
> +		ret = tun_put_user_xdp(tun, tfile, xdp, to);
> +		put_page(virt_to_head_page(xdp->data));

This again tie us info a page-refcnt based XDP scheme.


> +	} else {
> +		struct sk_buff *skb = ptr;
> +
> +		ret = tun_put_user(tun, tfile, skb, to);
> +		if (unlikely(ret < 0))
> +			kfree_skb(skb);
> +		else
> +			consume_skb(skb);
> +	}
>  
>  	return ret;
>  }
[...]

> @@ -3191,8 +3323,7 @@ struct socket *tun_get_socket(struct file *file)
>  	struct tun_file *tfile;
>  	if (file->f_op != &tun_fops)
>  		return ERR_PTR(-EINVAL);
> -	tfile = file->private_data;
> -	if (!tfile)
> +	tfile = file->private_data;	if (!tfile)

This change looks like a typo...

>  		return ERR_PTR(-EBADFD);
>  	return &tfile->socket;
>  }
kbuild test robot Jan. 1, 2018, 3:48 a.m. UTC | #2
Hi Jason,

I love your patch! Perhaps something to improve:

[auto build test WARNING on net-next/master]

url:    https://github.com/0day-ci/linux/commits/Jason-Wang/XDP-transmission-for-tuntap/20180101-105946
config: i386-randconfig-x072-201800 (attached as .config)
compiler: gcc-7 (Debian 7.2.0-12) 7.2.1 20171025
reproduce:
        # save the attached .config to linux build tree
        make ARCH=i386 

All warnings (new ones prefixed by >>):

   drivers//net/tun.c: In function 'tun_xdp_to_ptr':
>> drivers//net/tun.c:251:9: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]
     return (void *)((unsigned long)ptr | TUN_XDP_FLAG);
            ^
   drivers//net/tun.c: In function 'tun_ptr_to_xdp':
   drivers//net/tun.c:257:9: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]
     return (void *)((unsigned long)ptr & ~TUN_XDP_FLAG);
            ^

vim +251 drivers//net/tun.c

   248	
   249	void *tun_xdp_to_ptr(void *ptr)
   250	{
 > 251		return (void *)((unsigned long)ptr | TUN_XDP_FLAG);
   252	}
   253	EXPORT_SYMBOL(tun_xdp_to_ptr);
   254	

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation
Joe Perches Jan. 1, 2018, 3:55 a.m. UTC | #3
On Mon, 2018-01-01 at 11:48 +0800, kbuild test robot wrote:
> Hi Jason,
> 
> I love your patch! Perhaps something to improve:
> 
> [auto build test WARNING on net-next/master]
> 
> url:    https://github.com/0day-ci/linux/commits/Jason-Wang/XDP-transmission-for-tuntap/20180101-105946
> config: i386-randconfig-x072-201800 (attached as .config)
> compiler: gcc-7 (Debian 7.2.0-12) 7.2.1 20171025
> reproduce:
>         # save the attached .config to linux build tree
>         make ARCH=i386 
> 
> All warnings (new ones prefixed by >>):
> 
>    drivers//net/tun.c: In function 'tun_xdp_to_ptr':
> > > drivers//net/tun.c:251:9: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]
> 
>      return (void *)((unsigned long)ptr | TUN_XDP_FLAG);
>             ^
>    drivers//net/tun.c: In function 'tun_ptr_to_xdp':
>    drivers//net/tun.c:257:9: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]
>      return (void *)((unsigned long)ptr & ~TUN_XDP_FLAG);
>             ^
> 
> vim +251 drivers//net/tun.c
> 
>    248	
>    249	void *tun_xdp_to_ptr(void *ptr)
>    250	{
>  > 251		return (void *)((unsigned long)ptr | TUN_XDP_FLAG);

Does TUN_XDP_FLAG really need to be 0x1ULL?
Wouldn't 0x1UL suffice?
kbuild test robot Jan. 1, 2018, 4:09 a.m. UTC | #4
Hi Jason,

I love your patch! Yet something to improve:

[auto build test ERROR on net-next/master]

url:    https://github.com/0day-ci/linux/commits/Jason-Wang/XDP-transmission-for-tuntap/20180101-105946
config: i386-randconfig-s1-201800 (attached as .config)
compiler: gcc-6 (Debian 6.4.0-9) 6.4.0 20171026
reproduce:
        # save the attached .config to linux build tree
        make ARCH=i386 

All errors (new ones prefixed by >>):

   net/socket.o: In function `tun_xdp_to_ptr':
>> net/socket.c:3363: multiple definition of `tun_xdp_to_ptr'
   drivers/vhost/net.o:drivers/vhost/net.c:1026: first defined here
   net/socket.o: In function `tun_ptr_to_xdp':
>> include/linux/if_tun.h:50: multiple definition of `tun_ptr_to_xdp'
   drivers/vhost/net.o:include/linux/if_tun.h:50: first defined here

vim +3363 net/socket.c

ac5a488e Sridhar Samudrala 2006-08-07  3352  
306b13eb Tom Herbert       2017-07-28  3353  int kernel_sendpage_locked(struct sock *sk, struct page *page, int offset,
306b13eb Tom Herbert       2017-07-28  3354  			   size_t size, int flags)
306b13eb Tom Herbert       2017-07-28  3355  {
306b13eb Tom Herbert       2017-07-28  3356  	struct socket *sock = sk->sk_socket;
306b13eb Tom Herbert       2017-07-28  3357  
306b13eb Tom Herbert       2017-07-28  3358  	if (sock->ops->sendpage_locked)
306b13eb Tom Herbert       2017-07-28  3359  		return sock->ops->sendpage_locked(sk, page, offset, size,
306b13eb Tom Herbert       2017-07-28  3360  						  flags);
306b13eb Tom Herbert       2017-07-28  3361  
306b13eb Tom Herbert       2017-07-28  3362  	return sock_no_sendpage_locked(sk, page, offset, size, flags);
306b13eb Tom Herbert       2017-07-28 @3363  }
306b13eb Tom Herbert       2017-07-28  3364  EXPORT_SYMBOL(kernel_sendpage_locked);
306b13eb Tom Herbert       2017-07-28  3365  

:::::: The code at line 3363 was first introduced by commit
:::::: 306b13eb3cf9515a8214bbf5d69d811371d05792 proto_ops: Add locked held versions of sendmsg and sendpage

:::::: TO: Tom Herbert <tom@quantonium.net>
:::::: CC: David S. Miller <davem@davemloft.net>

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation
kbuild test robot Jan. 1, 2018, 8:27 a.m. UTC | #5
Hi Jason,

I love your patch! Perhaps something to improve:

[auto build test WARNING on net-next/master]

url:    https://github.com/0day-ci/linux/commits/Jason-Wang/XDP-transmission-for-tuntap/20180101-105946
reproduce:
        # apt-get install sparse
        make ARCH=x86_64 allmodconfig
        make C=1 CF=-D__CHECK_ENDIAN__


sparse warnings: (new ones prefixed by >>)


vim +1274 drivers/net/tun.c

  1270	
  1271	static void tun_xdp_flush(struct net_device *dev)
  1272	{
  1273		struct tun_struct *tun = netdev_priv(dev);
> 1274		struct tun_file *tfile = tun->tfiles[0];
  1275	
  1276		/* Notify and wake up reader process */
  1277		if (tfile->flags & TUN_FASYNC)
  1278			kill_fasync(&tfile->fasync, SIGIO, POLL_IN);
  1279		tfile->socket.sk->sk_data_ready(tfile->socket.sk);
  1280	}
  1281	

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation
Jason Wang Jan. 2, 2018, 3 a.m. UTC | #6
On 2017年12月29日 20:32, Jesper Dangaard Brouer wrote:
> On Fri, 29 Dec 2017 18:00:04 +0800
> Jason Wang <jasowang@redhat.com> wrote:
>
>> This patch implements XDP transmission for TAP. Since we can't create
>> new queues for TAP during XDP set, exist ptr_ring was reused for
>> queuing XDP buffers. To differ xdp_buff from sk_buff, TUN_XDP_FLAG
>> (0x1ULL) was encoded into lowest bit of xpd_buff pointer during
>> ptr_ring_produce, and was decoded during consuming. XDP metadata was
>> stored in the headroom of the packet which should work in most of
>> cases since driver usually reserve enough headroom. Very minor changes
>> were done for vhost_net: it just need to peek the length depends on
>> the type of pointer.
>>
>> Tests was done on two Intel E5-2630 2.40GHz machines connected back to
>> back through two 82599ES. Traffic were generated through MoonGen and
>> testpmd(rxonly) in guest reports 2.97Mpps when xdp_redirect_map is
>> doing redirection from ixgbe to TAP.
> IMHO a performance measurement without something to compare against is
> useless.  What was the performance before?

Forgot to mention, skb mode of xdp_redirect_map gives us 2.5Mpps, so we 
get about 20% improvements.

>
>
>> Cc: Jesper Dangaard Brouer <brouer@redhat.com>
>> Signed-off-by: Jason Wang <jasowang@redhat.com>
>> ---
>>   drivers/net/tun.c      | 205 ++++++++++++++++++++++++++++++++++++++++---------
>>   drivers/vhost/net.c    |  13 +++-
>>   include/linux/if_tun.h |  17 ++++
>>   3 files changed, 197 insertions(+), 38 deletions(-)
>>
>> diff --git a/drivers/net/tun.c b/drivers/net/tun.c
>> index 2c89efe..be6d993 100644
>> --- a/drivers/net/tun.c
>> +++ b/drivers/net/tun.c
>> @@ -240,6 +240,24 @@ struct tun_struct {
>>   	struct tun_steering_prog __rcu *steering_prog;
>>   };
>>   
>> +bool tun_is_xdp_buff(void *ptr)
>> +{
>> +	return (unsigned long)ptr & TUN_XDP_FLAG;
>> +}
>> +EXPORT_SYMBOL(tun_is_xdp_buff);
>> +
>> +void *tun_xdp_to_ptr(void *ptr)
>> +{
>> +	return (void *)((unsigned long)ptr | TUN_XDP_FLAG);
>> +}
>> +EXPORT_SYMBOL(tun_xdp_to_ptr);
>> +
>> +void *tun_ptr_to_xdp(void *ptr)
>> +{
>> +	return (void *)((unsigned long)ptr & ~TUN_XDP_FLAG);
>> +}
>> +EXPORT_SYMBOL(tun_ptr_to_xdp);
>> +
>>   static int tun_napi_receive(struct napi_struct *napi, int budget)
>>   {
>>   	struct tun_file *tfile = container_of(napi, struct tun_file, napi);
>> @@ -630,12 +648,25 @@ static struct tun_struct *tun_enable_queue(struct tun_file *tfile)
>>   	return tun;
>>   }
>>   
>> +static void tun_ptr_free(void *ptr)
>> +{
>> +	if (!ptr)
>> +		return;
>> +	if (tun_is_xdp_buff(ptr)) {
>> +		struct xdp_buff *xdp = tun_ptr_to_xdp(ptr);
>> +
>> +		put_page(virt_to_head_page(xdp->data));
> (Yet another XDP-free call point, I need to convert to use an accessor
> later to transition driver into an xdp_buff return API)

That would be very useful if we can reuse driver specific recycling 
method. (We probably still need some synchronization since tun_ptr_free 
is called in different contexts).

>
>> +	} else {
>> +		__skb_array_destroy_skb(ptr);
>> +	}
>> +}
>> +
>>   static void tun_queue_purge(struct tun_file *tfile)
>>   {
>> -	struct sk_buff *skb;
>> +	void *ptr;
>>   
>> -	while ((skb = ptr_ring_consume(&tfile->tx_ring)) != NULL)
>> -		kfree_skb(skb);
>> +	while ((ptr = ptr_ring_consume(&tfile->tx_ring)) != NULL)
>> +		tun_ptr_free(ptr);
>>   
>>   	skb_queue_purge(&tfile->sk.sk_write_queue);
>>   	skb_queue_purge(&tfile->sk.sk_error_queue);
>> @@ -688,8 +719,7 @@ static void __tun_detach(struct tun_file *tfile, bool clean)
>>   				unregister_netdevice(tun->dev);
>>   		}
>>   		if (tun)
>> -			ptr_ring_cleanup(&tfile->tx_ring,
>> -					 __skb_array_destroy_skb);
>> +			ptr_ring_cleanup(&tfile->tx_ring, tun_ptr_free);
>>   		sock_put(&tfile->sk);
>>   	}
>>   }
>> @@ -1201,6 +1231,54 @@ static const struct net_device_ops tun_netdev_ops = {
>>   	.ndo_get_stats64	= tun_net_get_stats64,
>>   };
>>   
>> +static int tun_xdp_xmit(struct net_device *dev, struct xdp_buff *xdp)
>> +{
>> +	struct tun_struct *tun = netdev_priv(dev);
>> +	struct xdp_buff *buff = xdp->data_hard_start;
>> +	int headroom = xdp->data - xdp->data_hard_start;
>> +	struct tun_file *tfile;
>> +	u32 numqueues;
>> +	int ret = 0;
>> +
>> +	/* Assure headroom is available and buff is properly aligned */
>> +	if (unlikely(headroom < sizeof(*xdp) || tun_is_xdp_buff(xdp)))
>> +		return -ENOSPC;
>> +
>> +	*buff = *xdp;
>> +
>> +	rcu_read_lock();
>> +
>> +	numqueues = READ_ONCE(tun->numqueues);
>> +	if (!numqueues) {
>> +		ret = -ENOSPC;
>> +		goto out;
>> +	}
>> +	tfile = rcu_dereference(tun->tfiles[smp_processor_id() %
>> +					    numqueues]);
> Several concurrent CPUs can get the same 'tfile'.

Yes, but I believe we don't have better solution here since we can do 
per-cpu queues without notifying guest (userspace).

>
>> +	/* Encode the XDP flag into lowest bit for consumer to differ
>> +	 * XDP buffer from sk_buff.
>> +	 */
>> +	if (ptr_ring_produce(&tfile->tx_ring, tun_xdp_to_ptr(buff))) {
>> +		this_cpu_inc(tun->pcpu_stats->tx_dropped);
>> +		ret = -ENOSPC;
>> +	}
> The ptr_ring_produce() will take a lock per packet, limiting the
> performance.  (Again a case where I would have liked a bulk API for
> ndo_xdp_xmit()).

Yes and I'm considering to use your bulk method in cpu map on top in the 
future. The main bottleneck now is not ndo_xdp_xmit() (8Mpps) but vhost 
(3Mpps), with some improvements, vhost an have about 7Mpps but still 
slower than ndo_xdp_xmit().

>
>
>> +
>> +out:
>> +	rcu_read_unlock();
>> +	return ret;
>> +}
>> +
>> +static void tun_xdp_flush(struct net_device *dev)
>> +{
>> +	struct tun_struct *tun = netdev_priv(dev);
>> +	struct tun_file *tfile = tun->tfiles[0];
>> +
>> +	/* Notify and wake up reader process */
>> +	if (tfile->flags & TUN_FASYNC)
>> +		kill_fasync(&tfile->fasync, SIGIO, POLL_IN);
>> +	tfile->socket.sk->sk_data_ready(tfile->socket.sk);
>> +}
>> +
>>   static const struct net_device_ops tap_netdev_ops = {
>>   	.ndo_uninit		= tun_net_uninit,
>>   	.ndo_open		= tun_net_open,
>> @@ -1218,6 +1296,8 @@ static const struct net_device_ops tap_netdev_ops = {
>>   	.ndo_set_rx_headroom	= tun_set_headroom,
>>   	.ndo_get_stats64	= tun_net_get_stats64,
>>   	.ndo_bpf		= tun_xdp,
>> +	.ndo_xdp_xmit		= tun_xdp_xmit,
>> +	.ndo_xdp_flush		= tun_xdp_flush,
>>   };
>>   
>>   static void tun_flow_init(struct tun_struct *tun)
>> @@ -1841,6 +1921,40 @@ static ssize_t tun_chr_write_iter(struct kiocb *iocb, struct iov_iter *from)
>>   	return result;
>>   }
>>   
>> +static ssize_t tun_put_user_xdp(struct tun_struct *tun,
>> +				struct tun_file *tfile,
>> +				struct xdp_buff *xdp,
>> +				struct iov_iter *iter)
>> +{
>> +	int vnet_hdr_sz = 0;
>> +	size_t size = xdp->data_end - xdp->data;
>> +	struct tun_pcpu_stats *stats;
>> +	size_t ret;
>> +
>> +	if (tun->flags & IFF_VNET_HDR) {
>> +		struct virtio_net_hdr gso = { 0 };
>> +
>> +		vnet_hdr_sz = READ_ONCE(tun->vnet_hdr_sz);
>> +		if (unlikely(iov_iter_count(iter) < vnet_hdr_sz))
>> +			return -EINVAL;
>> +		if (unlikely(copy_to_iter(&gso, sizeof(gso), iter) !=
>> +			     sizeof(gso)))
>> +			return -EFAULT;
>> +		iov_iter_advance(iter, vnet_hdr_sz - sizeof(gso));
>> +	}
>> +
>> +	ret = copy_to_iter(xdp->data, size, iter) + vnet_hdr_sz;
>> +
>> +	stats = get_cpu_ptr(tun->pcpu_stats);
>> +	u64_stats_update_begin(&stats->syncp);
>> +	stats->tx_packets++;
>> +	stats->tx_bytes += ret;
>> +	u64_stats_update_end(&stats->syncp);
>> +	put_cpu_ptr(tun->pcpu_stats);
>> +
>> +	return ret;
>> +}
>> +
>>   /* Put packet to the user space buffer */
>>   static ssize_t tun_put_user(struct tun_struct *tun,
>>   			    struct tun_file *tfile,
> [...]
>>   			error = -ERESTARTSYS;
>> @@ -1977,36 +2090,42 @@ static struct sk_buff *tun_ring_recv(struct tun_file *tfile, int noblock,
>>   
>>   out:
>>   	*err = error;
>> -	return skb;
>> +	return ptr;
>>   }
>>   
>>   static ssize_t tun_do_read(struct tun_struct *tun, struct tun_file *tfile,
>>   			   struct iov_iter *to,
>> -			   int noblock, struct sk_buff *skb)
>> +			   int noblock, void *ptr)
>>   {
>>   	ssize_t ret;
>>   	int err;
>>   
>>   	tun_debug(KERN_INFO, tun, "tun_do_read\n");
>>   
>> -	if (!iov_iter_count(to)) {
>> -		if (skb)
>> -			kfree_skb(skb);
>> -		return 0;
>> -	}
>> +	if (!iov_iter_count(to))
>> +		tun_ptr_free(ptr);
>>   
>> -	if (!skb) {
>> +	if (!ptr) {
>>   		/* Read frames from ring */
>> -		skb = tun_ring_recv(tfile, noblock, &err);
>> -		if (!skb)
>> +		ptr = tun_ring_recv(tfile, noblock, &err);
>> +		if (!ptr)
>>   			return err;
>>   	}
>>   
>> -	ret = tun_put_user(tun, tfile, skb, to);
>> -	if (unlikely(ret < 0))
>> -		kfree_skb(skb);
>> -	else
>> -		consume_skb(skb);
>> +	if (tun_is_xdp_buff(ptr)) {
>> +		struct xdp_buff *xdp = tun_ptr_to_xdp(ptr);
>> +
>> +		ret = tun_put_user_xdp(tun, tfile, xdp, to);
>> +		put_page(virt_to_head_page(xdp->data));
> This again tie us info a page-refcnt based XDP scheme.
>
>
>> +	} else {
>> +		struct sk_buff *skb = ptr;
>> +
>> +		ret = tun_put_user(tun, tfile, skb, to);
>> +		if (unlikely(ret < 0))
>> +			kfree_skb(skb);
>> +		else
>> +			consume_skb(skb);
>> +	}
>>   
>>   	return ret;
>>   }
> [...]
>
>> @@ -3191,8 +3323,7 @@ struct socket *tun_get_socket(struct file *file)
>>   	struct tun_file *tfile;
>>   	if (file->f_op != &tun_fops)
>>   		return ERR_PTR(-EINVAL);
>> -	tfile = file->private_data;
>> -	if (!tfile)
>> +	tfile = file->private_data;	if (!tfile)
> This change looks like a typo...

Oops, will fix this.

Thanks!

>
>>   		return ERR_PTR(-EBADFD);
>>   	return &tfile->socket;
>>   }
>
Jason Wang Jan. 2, 2018, 3:32 a.m. UTC | #7
On 2018年01月01日 11:55, Joe Perches wrote:
> On Mon, 2018-01-01 at 11:48 +0800, kbuild test robot wrote:
>> Hi Jason,
>>
>> I love your patch! Perhaps something to improve:
>>
>> [auto build test WARNING on net-next/master]
>>
>> url:    https://github.com/0day-ci/linux/commits/Jason-Wang/XDP-transmission-for-tuntap/20180101-105946
>> config: i386-randconfig-x072-201800 (attached as .config)
>> compiler: gcc-7 (Debian 7.2.0-12) 7.2.1 20171025
>> reproduce:
>>          # save the attached .config to linux build tree
>>          make ARCH=i386
>>
>> All warnings (new ones prefixed by >>):
>>
>>     drivers//net/tun.c: In function 'tun_xdp_to_ptr':
>>>> drivers//net/tun.c:251:9: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]
>>       return (void *)((unsigned long)ptr | TUN_XDP_FLAG);
>>              ^
>>     drivers//net/tun.c: In function 'tun_ptr_to_xdp':
>>     drivers//net/tun.c:257:9: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]
>>       return (void *)((unsigned long)ptr & ~TUN_XDP_FLAG);
>>              ^
>>
>> vim +251 drivers//net/tun.c
>>
>>     248	
>>     249	void *tun_xdp_to_ptr(void *ptr)
>>     250	{
>>   > 251		return (void *)((unsigned long)ptr | TUN_XDP_FLAG);
> Does TUN_XDP_FLAG really need to be 0x1ULL?
> Wouldn't 0x1UL suffice?
>

0x1UL should be fine.

Thanks

Patch
diff mbox series

diff --git a/drivers/net/tun.c b/drivers/net/tun.c
index 2c89efe..be6d993 100644
--- a/drivers/net/tun.c
+++ b/drivers/net/tun.c
@@ -240,6 +240,24 @@  struct tun_struct {
 	struct tun_steering_prog __rcu *steering_prog;
 };
 
+bool tun_is_xdp_buff(void *ptr)
+{
+	return (unsigned long)ptr & TUN_XDP_FLAG;
+}
+EXPORT_SYMBOL(tun_is_xdp_buff);
+
+void *tun_xdp_to_ptr(void *ptr)
+{
+	return (void *)((unsigned long)ptr | TUN_XDP_FLAG);
+}
+EXPORT_SYMBOL(tun_xdp_to_ptr);
+
+void *tun_ptr_to_xdp(void *ptr)
+{
+	return (void *)((unsigned long)ptr & ~TUN_XDP_FLAG);
+}
+EXPORT_SYMBOL(tun_ptr_to_xdp);
+
 static int tun_napi_receive(struct napi_struct *napi, int budget)
 {
 	struct tun_file *tfile = container_of(napi, struct tun_file, napi);
@@ -630,12 +648,25 @@  static struct tun_struct *tun_enable_queue(struct tun_file *tfile)
 	return tun;
 }
 
+static void tun_ptr_free(void *ptr)
+{
+	if (!ptr)
+		return;
+	if (tun_is_xdp_buff(ptr)) {
+		struct xdp_buff *xdp = tun_ptr_to_xdp(ptr);
+
+		put_page(virt_to_head_page(xdp->data));
+	} else {
+		__skb_array_destroy_skb(ptr);
+	}
+}
+
 static void tun_queue_purge(struct tun_file *tfile)
 {
-	struct sk_buff *skb;
+	void *ptr;
 
-	while ((skb = ptr_ring_consume(&tfile->tx_ring)) != NULL)
-		kfree_skb(skb);
+	while ((ptr = ptr_ring_consume(&tfile->tx_ring)) != NULL)
+		tun_ptr_free(ptr);
 
 	skb_queue_purge(&tfile->sk.sk_write_queue);
 	skb_queue_purge(&tfile->sk.sk_error_queue);
@@ -688,8 +719,7 @@  static void __tun_detach(struct tun_file *tfile, bool clean)
 				unregister_netdevice(tun->dev);
 		}
 		if (tun)
-			ptr_ring_cleanup(&tfile->tx_ring,
-					 __skb_array_destroy_skb);
+			ptr_ring_cleanup(&tfile->tx_ring, tun_ptr_free);
 		sock_put(&tfile->sk);
 	}
 }
@@ -1201,6 +1231,54 @@  static const struct net_device_ops tun_netdev_ops = {
 	.ndo_get_stats64	= tun_net_get_stats64,
 };
 
+static int tun_xdp_xmit(struct net_device *dev, struct xdp_buff *xdp)
+{
+	struct tun_struct *tun = netdev_priv(dev);
+	struct xdp_buff *buff = xdp->data_hard_start;
+	int headroom = xdp->data - xdp->data_hard_start;
+	struct tun_file *tfile;
+	u32 numqueues;
+	int ret = 0;
+
+	/* Assure headroom is available and buff is properly aligned */
+	if (unlikely(headroom < sizeof(*xdp) || tun_is_xdp_buff(xdp)))
+		return -ENOSPC;
+
+	*buff = *xdp;
+
+	rcu_read_lock();
+
+	numqueues = READ_ONCE(tun->numqueues);
+	if (!numqueues) {
+		ret = -ENOSPC;
+		goto out;
+	}
+	tfile = rcu_dereference(tun->tfiles[smp_processor_id() %
+					    numqueues]);
+	/* Encode the XDP flag into lowest bit for consumer to differ
+	 * XDP buffer from sk_buff.
+	 */
+	if (ptr_ring_produce(&tfile->tx_ring, tun_xdp_to_ptr(buff))) {
+		this_cpu_inc(tun->pcpu_stats->tx_dropped);
+		ret = -ENOSPC;
+	}
+
+out:
+	rcu_read_unlock();
+	return ret;
+}
+
+static void tun_xdp_flush(struct net_device *dev)
+{
+	struct tun_struct *tun = netdev_priv(dev);
+	struct tun_file *tfile = tun->tfiles[0];
+
+	/* Notify and wake up reader process */
+	if (tfile->flags & TUN_FASYNC)
+		kill_fasync(&tfile->fasync, SIGIO, POLL_IN);
+	tfile->socket.sk->sk_data_ready(tfile->socket.sk);
+}
+
 static const struct net_device_ops tap_netdev_ops = {
 	.ndo_uninit		= tun_net_uninit,
 	.ndo_open		= tun_net_open,
@@ -1218,6 +1296,8 @@  static const struct net_device_ops tap_netdev_ops = {
 	.ndo_set_rx_headroom	= tun_set_headroom,
 	.ndo_get_stats64	= tun_net_get_stats64,
 	.ndo_bpf		= tun_xdp,
+	.ndo_xdp_xmit		= tun_xdp_xmit,
+	.ndo_xdp_flush		= tun_xdp_flush,
 };
 
 static void tun_flow_init(struct tun_struct *tun)
@@ -1841,6 +1921,40 @@  static ssize_t tun_chr_write_iter(struct kiocb *iocb, struct iov_iter *from)
 	return result;
 }
 
+static ssize_t tun_put_user_xdp(struct tun_struct *tun,
+				struct tun_file *tfile,
+				struct xdp_buff *xdp,
+				struct iov_iter *iter)
+{
+	int vnet_hdr_sz = 0;
+	size_t size = xdp->data_end - xdp->data;
+	struct tun_pcpu_stats *stats;
+	size_t ret;
+
+	if (tun->flags & IFF_VNET_HDR) {
+		struct virtio_net_hdr gso = { 0 };
+
+		vnet_hdr_sz = READ_ONCE(tun->vnet_hdr_sz);
+		if (unlikely(iov_iter_count(iter) < vnet_hdr_sz))
+			return -EINVAL;
+		if (unlikely(copy_to_iter(&gso, sizeof(gso), iter) !=
+			     sizeof(gso)))
+			return -EFAULT;
+		iov_iter_advance(iter, vnet_hdr_sz - sizeof(gso));
+	}
+
+	ret = copy_to_iter(xdp->data, size, iter) + vnet_hdr_sz;
+
+	stats = get_cpu_ptr(tun->pcpu_stats);
+	u64_stats_update_begin(&stats->syncp);
+	stats->tx_packets++;
+	stats->tx_bytes += ret;
+	u64_stats_update_end(&stats->syncp);
+	put_cpu_ptr(tun->pcpu_stats);
+
+	return ret;
+}
+
 /* Put packet to the user space buffer */
 static ssize_t tun_put_user(struct tun_struct *tun,
 			    struct tun_file *tfile,
@@ -1938,15 +2052,14 @@  static ssize_t tun_put_user(struct tun_struct *tun,
 	return total;
 }
 
-static struct sk_buff *tun_ring_recv(struct tun_file *tfile, int noblock,
-				     int *err)
+static void *tun_ring_recv(struct tun_file *tfile, int noblock, int *err)
 {
 	DECLARE_WAITQUEUE(wait, current);
-	struct sk_buff *skb = NULL;
+	void *ptr = NULL;
 	int error = 0;
 
-	skb = ptr_ring_consume(&tfile->tx_ring);
-	if (skb)
+	ptr = ptr_ring_consume(&tfile->tx_ring);
+	if (ptr)
 		goto out;
 	if (noblock) {
 		error = -EAGAIN;
@@ -1957,8 +2070,8 @@  static struct sk_buff *tun_ring_recv(struct tun_file *tfile, int noblock,
 	current->state = TASK_INTERRUPTIBLE;
 
 	while (1) {
-		skb = ptr_ring_consume(&tfile->tx_ring);
-		if (skb)
+		ptr = ptr_ring_consume(&tfile->tx_ring);
+		if (ptr)
 			break;
 		if (signal_pending(current)) {
 			error = -ERESTARTSYS;
@@ -1977,36 +2090,42 @@  static struct sk_buff *tun_ring_recv(struct tun_file *tfile, int noblock,
 
 out:
 	*err = error;
-	return skb;
+	return ptr;
 }
 
 static ssize_t tun_do_read(struct tun_struct *tun, struct tun_file *tfile,
 			   struct iov_iter *to,
-			   int noblock, struct sk_buff *skb)
+			   int noblock, void *ptr)
 {
 	ssize_t ret;
 	int err;
 
 	tun_debug(KERN_INFO, tun, "tun_do_read\n");
 
-	if (!iov_iter_count(to)) {
-		if (skb)
-			kfree_skb(skb);
-		return 0;
-	}
+	if (!iov_iter_count(to))
+		tun_ptr_free(ptr);
 
-	if (!skb) {
+	if (!ptr) {
 		/* Read frames from ring */
-		skb = tun_ring_recv(tfile, noblock, &err);
-		if (!skb)
+		ptr = tun_ring_recv(tfile, noblock, &err);
+		if (!ptr)
 			return err;
 	}
 
-	ret = tun_put_user(tun, tfile, skb, to);
-	if (unlikely(ret < 0))
-		kfree_skb(skb);
-	else
-		consume_skb(skb);
+	if (tun_is_xdp_buff(ptr)) {
+		struct xdp_buff *xdp = tun_ptr_to_xdp(ptr);
+
+		ret = tun_put_user_xdp(tun, tfile, xdp, to);
+		put_page(virt_to_head_page(xdp->data));
+	} else {
+		struct sk_buff *skb = ptr;
+
+		ret = tun_put_user(tun, tfile, skb, to);
+		if (unlikely(ret < 0))
+			kfree_skb(skb);
+		else
+			consume_skb(skb);
+	}
 
 	return ret;
 }
@@ -2143,12 +2262,12 @@  static int tun_recvmsg(struct socket *sock, struct msghdr *m, size_t total_len,
 {
 	struct tun_file *tfile = container_of(sock, struct tun_file, socket);
 	struct tun_struct *tun = tun_get(tfile);
-	struct sk_buff *skb = m->msg_control;
+	void *ptr = m->msg_control;
 	int ret;
 
 	if (!tun) {
 		ret = -EBADFD;
-		goto out_free_skb;
+		goto out_free;
 	}
 
 	if (flags & ~(MSG_DONTWAIT|MSG_TRUNC|MSG_ERRQUEUE)) {
@@ -2160,7 +2279,7 @@  static int tun_recvmsg(struct socket *sock, struct msghdr *m, size_t total_len,
 					 SOL_PACKET, TUN_TX_TIMESTAMP);
 		goto out;
 	}
-	ret = tun_do_read(tun, tfile, &m->msg_iter, flags & MSG_DONTWAIT, skb);
+	ret = tun_do_read(tun, tfile, &m->msg_iter, flags & MSG_DONTWAIT, ptr);
 	if (ret > (ssize_t)total_len) {
 		m->msg_flags |= MSG_TRUNC;
 		ret = flags & MSG_TRUNC ? ret : total_len;
@@ -2171,12 +2290,25 @@  static int tun_recvmsg(struct socket *sock, struct msghdr *m, size_t total_len,
 
 out_put_tun:
 	tun_put(tun);
-out_free_skb:
-	if (skb)
-		kfree_skb(skb);
+out_free:
+	tun_ptr_free(ptr);
 	return ret;
 }
 
+static int tun_ptr_peek_len(void *ptr)
+{
+	if (likely(ptr)) {
+		if (tun_is_xdp_buff(ptr)) {
+			struct xdp_buff *xdp = tun_ptr_to_xdp(ptr);
+
+			return xdp->data_end - xdp->data;
+		}
+		return __skb_array_len_with_tag(ptr);
+	} else {
+		return 0;
+	}
+}
+
 static int tun_peek_len(struct socket *sock)
 {
 	struct tun_file *tfile = container_of(sock, struct tun_file, socket);
@@ -2187,7 +2319,7 @@  static int tun_peek_len(struct socket *sock)
 	if (!tun)
 		return 0;
 
-	ret = PTR_RING_PEEK_CALL(&tfile->tx_ring, __skb_array_len_with_tag);
+	ret = PTR_RING_PEEK_CALL(&tfile->tx_ring, tun_ptr_peek_len);
 	tun_put(tun);
 
 	return ret;
@@ -3110,7 +3242,7 @@  static int tun_queue_resize(struct tun_struct *tun)
 
 	ret = ptr_ring_resize_multiple(rings, n,
 				       dev->tx_queue_len, GFP_KERNEL,
-				       __skb_array_destroy_skb);
+				       tun_ptr_free);
 
 	kfree(rings);
 	return ret;
@@ -3191,8 +3323,7 @@  struct socket *tun_get_socket(struct file *file)
 	struct tun_file *tfile;
 	if (file->f_op != &tun_fops)
 		return ERR_PTR(-EINVAL);
-	tfile = file->private_data;
-	if (!tfile)
+	tfile = file->private_data;	if (!tfile)
 		return ERR_PTR(-EBADFD);
 	return &tfile->socket;
 }
diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c
index c316555..a5a1db6 100644
--- a/drivers/vhost/net.c
+++ b/drivers/vhost/net.c
@@ -175,6 +175,17 @@  static void vhost_net_buf_unproduce(struct vhost_net_virtqueue *nvq)
 	}
 }
 
+static int vhost_net_buf_peek_len(void *ptr)
+{
+	if (tun_is_xdp_buff(ptr)) {
+		struct xdp_buff *xdp = tun_ptr_to_xdp(ptr);
+
+		return xdp->data_end - xdp->data;
+	}
+
+	return __skb_array_len_with_tag(ptr);
+}
+
 static int vhost_net_buf_peek(struct vhost_net_virtqueue *nvq)
 {
 	struct vhost_net_buf *rxq = &nvq->rxq;
@@ -186,7 +197,7 @@  static int vhost_net_buf_peek(struct vhost_net_virtqueue *nvq)
 		return 0;
 
 out:
-	return __skb_array_len_with_tag(vhost_net_buf_get_ptr(rxq));
+	return vhost_net_buf_peek_len(vhost_net_buf_get_ptr(rxq));
 }
 
 static void vhost_net_buf_init(struct vhost_net_buf *rxq)
diff --git a/include/linux/if_tun.h b/include/linux/if_tun.h
index bdee9b8..1cafdc2 100644
--- a/include/linux/if_tun.h
+++ b/include/linux/if_tun.h
@@ -17,9 +17,14 @@ 
 
 #include <uapi/linux/if_tun.h>
 
+#define TUN_XDP_FLAG 0x1ULL
+
 #if defined(CONFIG_TUN) || defined(CONFIG_TUN_MODULE)
 struct socket *tun_get_socket(struct file *);
 struct ptr_ring *tun_get_tx_ring(struct file *file);
+bool tun_is_xdp_buff(void *ptr);
+void *tun_xdp_to_ptr(void *ptr);
+void *tun_ptr_to_xdp(void *ptr);
 #else
 #include <linux/err.h>
 #include <linux/errno.h>
@@ -33,5 +38,17 @@  static inline struct ptr_ring *tun_get_tx_ring(struct file *f)
 {
 	return ERR_PTR(-EINVAL);
 }
+static inline bool tun_is_xdp_buff(void *ptr)
+{
+	return false;
+}
+void *tun_xdp_to_ptr(void *ptr)
+{
+	return NULL;
+}
+void *tun_ptr_to_xdp(void *ptr)
+{
+	return NULL;
+}
 #endif /* CONFIG_TUN */
 #endif /* __IF_TUN_H */