2026 lines · 47.8 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* *  drivers/net/veth.c * *  Copyright (C) 2007 OpenVZ http://openvz.org, SWsoft Inc * * Author: Pavel Emelianov <xemul@openvz.org> * Ethtool interface from: Eric W. Biederman <ebiederm@xmission.com> * */ #include <linux/netdevice.h>#include <linux/slab.h>#include <linux/ethtool.h>#include <linux/etherdevice.h>#include <linux/u64_stats_sync.h> #include <net/rtnetlink.h>#include <net/dst.h>#include <net/netdev_lock.h>#include <net/xfrm.h>#include <net/xdp.h>#include <linux/veth.h>#include <linux/module.h>#include <linux/bpf.h>#include <linux/filter.h>#include <linux/ptr_ring.h>#include <linux/bpf_trace.h>#include <linux/net_tstamp.h>#include <linux/skbuff_ref.h>#include <net/page_pool/helpers.h> #define DRV_NAME	"veth"#define DRV_VERSION	"1.0" #define VETH_XDP_FLAG		BIT(0)#define VETH_RING_SIZE		256#define VETH_XDP_HEADROOM	(XDP_PACKET_HEADROOM + NET_IP_ALIGN) #define VETH_XDP_TX_BULK_SIZE	16#define VETH_XDP_BATCH		16 struct veth_stats {	u64	rx_drops;	/* xdp */	u64	xdp_packets;	u64	xdp_bytes;	u64	xdp_redirect;	u64	xdp_drops;	u64	xdp_tx;	u64	xdp_tx_err;	u64	peer_tq_xdp_xmit;	u64	peer_tq_xdp_xmit_err;}; struct veth_rq_stats {	struct veth_stats	vs;	struct u64_stats_sync	syncp;}; struct veth_rq {	struct napi_struct	xdp_napi;	struct napi_struct __rcu *napi; /* points to xdp_napi when the latter is initialized */	struct net_device	*dev;	struct bpf_prog __rcu	*xdp_prog;	struct xdp_mem_info	xdp_mem;	struct veth_rq_stats	stats;	bool			rx_notify_masked;	struct ptr_ring		xdp_ring;	struct xdp_rxq_info	xdp_rxq;	struct page_pool	*page_pool;}; struct veth_priv {	struct net_device __rcu	*peer;	atomic64_t		dropped;	struct bpf_prog		*_xdp_prog;	struct veth_rq		*rq;	unsigned int		requested_headroom;	netdevice_tracker	peer_tracker;}; struct veth_xdp_tx_bq {	struct xdp_frame *q[VETH_XDP_TX_BULK_SIZE];	unsigned int count;}; /* * ethtool interface */ struct veth_q_stat_desc {	char	desc[ETH_GSTRING_LEN];	size_t	offset;}; #define VETH_RQ_STAT(m)	offsetof(struct veth_stats, m) static const struct veth_q_stat_desc veth_rq_stats_desc[] = {	{ "xdp_packets",	VETH_RQ_STAT(xdp_packets) },	{ "xdp_bytes",		VETH_RQ_STAT(xdp_bytes) },	{ "drops",		VETH_RQ_STAT(rx_drops) },	{ "xdp_redirect",	VETH_RQ_STAT(xdp_redirect) },	{ "xdp_drops",		VETH_RQ_STAT(xdp_drops) },	{ "xdp_tx",		VETH_RQ_STAT(xdp_tx) },	{ "xdp_tx_errors",	VETH_RQ_STAT(xdp_tx_err) },}; #define VETH_RQ_STATS_LEN	ARRAY_SIZE(veth_rq_stats_desc) static const struct veth_q_stat_desc veth_tq_stats_desc[] = {	{ "xdp_xmit",		VETH_RQ_STAT(peer_tq_xdp_xmit) },	{ "xdp_xmit_errors",	VETH_RQ_STAT(peer_tq_xdp_xmit_err) },}; #define VETH_TQ_STATS_LEN	ARRAY_SIZE(veth_tq_stats_desc) static struct {	const char string[ETH_GSTRING_LEN];} ethtool_stats_keys[] = {	{ "peer_ifindex" },}; struct veth_xdp_buff {	struct xdp_buff xdp;	struct sk_buff *skb;}; static int veth_get_link_ksettings(struct net_device *dev,				   struct ethtool_link_ksettings *cmd){	cmd->base.speed		= SPEED_10000;	cmd->base.duplex	= DUPLEX_FULL;	cmd->base.port		= PORT_TP;	cmd->base.autoneg	= AUTONEG_DISABLE;	return 0;} static void veth_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info){	strscpy(info->driver, DRV_NAME, sizeof(info->driver));	strscpy(info->version, DRV_VERSION, sizeof(info->version));} static void veth_get_strings(struct net_device *dev, u32 stringset, u8 *buf){	u8 *p = buf;	int i, j; 	switch(stringset) {	case ETH_SS_STATS:		memcpy(p, &ethtool_stats_keys, sizeof(ethtool_stats_keys));		p += sizeof(ethtool_stats_keys);		for (i = 0; i < dev->real_num_rx_queues; i++)			for (j = 0; j < VETH_RQ_STATS_LEN; j++)				ethtool_sprintf(&p, "rx_queue_%u_%.18s",						i, veth_rq_stats_desc[j].desc); 		for (i = 0; i < dev->real_num_tx_queues; i++)			for (j = 0; j < VETH_TQ_STATS_LEN; j++)				ethtool_sprintf(&p, "tx_queue_%u_%.18s",						i, veth_tq_stats_desc[j].desc); 		page_pool_ethtool_stats_get_strings(p);		break;	}} static int veth_get_sset_count(struct net_device *dev, int sset){	switch (sset) {	case ETH_SS_STATS:		return ARRAY_SIZE(ethtool_stats_keys) +		       VETH_RQ_STATS_LEN * dev->real_num_rx_queues +		       VETH_TQ_STATS_LEN * dev->real_num_tx_queues +		       page_pool_ethtool_stats_get_count();	default:		return -EOPNOTSUPP;	}} static void veth_get_page_pool_stats(struct net_device *dev, u64 *data){#ifdef CONFIG_PAGE_POOL_STATS	struct veth_priv *priv = netdev_priv(dev);	struct page_pool_stats pp_stats = {};	int i; 	for (i = 0; i < dev->real_num_rx_queues; i++) {		if (!priv->rq[i].page_pool)			continue;		page_pool_get_stats(priv->rq[i].page_pool, &pp_stats);	}	page_pool_ethtool_stats_get(data, &pp_stats);#endif /* CONFIG_PAGE_POOL_STATS */} static void veth_get_ethtool_stats(struct net_device *dev,		struct ethtool_stats *stats, u64 *data){	struct veth_priv *rcv_priv, *priv = netdev_priv(dev);	struct net_device *peer = rtnl_dereference(priv->peer);	int i, j, idx, pp_idx; 	data[0] = peer ? peer->ifindex : 0;	idx = 1;	for (i = 0; i < dev->real_num_rx_queues; i++) {		const struct veth_rq_stats *rq_stats = &priv->rq[i].stats;		const void *stats_base = (void *)&rq_stats->vs;		unsigned int start;		size_t offset; 		do {			start = u64_stats_fetch_begin(&rq_stats->syncp);			for (j = 0; j < VETH_RQ_STATS_LEN; j++) {				offset = veth_rq_stats_desc[j].offset;				data[idx + j] = *(u64 *)(stats_base + offset);			}		} while (u64_stats_fetch_retry(&rq_stats->syncp, start));		idx += VETH_RQ_STATS_LEN;	}	pp_idx = idx; 	if (!peer)		goto page_pool_stats; 	rcv_priv = netdev_priv(peer);	for (i = 0; i < peer->real_num_rx_queues; i++) {		const struct veth_rq_stats *rq_stats = &rcv_priv->rq[i].stats;		const void *base = (void *)&rq_stats->vs;		unsigned int start, tx_idx = idx;		u64 buf[VETH_TQ_STATS_LEN];		size_t offset; 		do {			start = u64_stats_fetch_begin(&rq_stats->syncp);			for (j = 0; j < VETH_TQ_STATS_LEN; j++) {				offset = veth_tq_stats_desc[j].offset;				buf[j] = *(u64 *)(base + offset);			}		} while (u64_stats_fetch_retry(&rq_stats->syncp, start)); 		tx_idx += (i % dev->real_num_tx_queues) * VETH_TQ_STATS_LEN;		for (j = 0; j < VETH_TQ_STATS_LEN; j++)			data[tx_idx + j] += buf[j];	}	pp_idx = idx + dev->real_num_tx_queues * VETH_TQ_STATS_LEN; page_pool_stats:	veth_get_page_pool_stats(dev, &data[pp_idx]);} static void veth_get_channels(struct net_device *dev,			      struct ethtool_channels *channels){	channels->tx_count = dev->real_num_tx_queues;	channels->rx_count = dev->real_num_rx_queues;	channels->max_tx = dev->num_tx_queues;	channels->max_rx = dev->num_rx_queues;} static int veth_set_channels(struct net_device *dev,			     struct ethtool_channels *ch); static const struct ethtool_ops veth_ethtool_ops = {	.get_drvinfo		= veth_get_drvinfo,	.get_link		= ethtool_op_get_link,	.get_strings		= veth_get_strings,	.get_sset_count		= veth_get_sset_count,	.get_ethtool_stats	= veth_get_ethtool_stats,	.get_link_ksettings	= veth_get_link_ksettings,	.get_ts_info		= ethtool_op_get_ts_info,	.get_channels		= veth_get_channels,	.set_channels		= veth_set_channels,}; /* general routines */ static bool veth_is_xdp_frame(void *ptr){	return (unsigned long)ptr & VETH_XDP_FLAG;} static struct xdp_frame *veth_ptr_to_xdp(void *ptr){	return (void *)((unsigned long)ptr & ~VETH_XDP_FLAG);} static void *veth_xdp_to_ptr(struct xdp_frame *xdp){	return (void *)((unsigned long)xdp | VETH_XDP_FLAG);} static void veth_ptr_free(void *ptr){	if (veth_is_xdp_frame(ptr))		xdp_return_frame(veth_ptr_to_xdp(ptr));	else		kfree_skb(ptr);} static void __veth_xdp_flush(struct veth_rq *rq){	/* Write ptr_ring before reading rx_notify_masked */	smp_mb();	if (!READ_ONCE(rq->rx_notify_masked) &&	    napi_schedule_prep(&rq->xdp_napi)) {		WRITE_ONCE(rq->rx_notify_masked, true);		__napi_schedule(&rq->xdp_napi);	}} static int veth_xdp_rx(struct veth_rq *rq, struct sk_buff *skb){	if (unlikely(ptr_ring_produce(&rq->xdp_ring, skb)))		return NETDEV_TX_BUSY; /* signal qdisc layer */ 	return NET_RX_SUCCESS; /* same as NETDEV_TX_OK */} static int veth_forward_skb(struct net_device *dev, struct sk_buff *skb,			    struct veth_rq *rq, bool xdp){	return __dev_forward_skb(dev, skb) ?: xdp ?		veth_xdp_rx(rq, skb) :		__netif_rx(skb);} /* return true if the specified skb has chances of GRO aggregation * Don't strive for accuracy, but try to avoid GRO overhead in the most * common scenarios. * When XDP is enabled, all traffic is considered eligible, as the xmit * device has TSO off. * When TSO is enabled on the xmit device, we are likely interested only * in UDP aggregation, explicitly check for that if the skb is suspected * - the sock_wfree destructor is used by UDP, ICMP and XDP sockets - * to belong to locally generated UDP traffic. */static bool veth_skb_is_eligible_for_gro(const struct net_device *dev,					 const struct net_device *rcv,					 const struct sk_buff *skb){	return !(dev->features & NETIF_F_ALL_TSO) ||		(skb->destructor == sock_wfree &&		 rcv->features & (NETIF_F_GRO_FRAGLIST | NETIF_F_GRO_UDP_FWD));} static netdev_tx_t veth_xmit(struct sk_buff *skb, struct net_device *dev){	struct veth_priv *rcv_priv, *priv = netdev_priv(dev);	struct veth_rq *rq = NULL;	struct netdev_queue *txq;	struct net_device *rcv;	int length = skb->len;	bool use_napi = false;	int ret, rxq; 	rcu_read_lock();	rcv = rcu_dereference(priv->peer);	if (unlikely(!rcv) || !pskb_may_pull(skb, ETH_HLEN)) {		kfree_skb(skb);		goto drop;	} 	rcv_priv = netdev_priv(rcv);	rxq = skb_get_queue_mapping(skb);	if (rxq < rcv->real_num_rx_queues) {		rq = &rcv_priv->rq[rxq]; 		/* The napi pointer is available when an XDP program is		 * attached or when GRO is enabled		 * Don't bother with napi/GRO if the skb can't be aggregated		 */		use_napi = rcu_access_pointer(rq->napi) &&			   veth_skb_is_eligible_for_gro(dev, rcv, skb);	} 	skb_tx_timestamp(skb); 	ret = veth_forward_skb(rcv, skb, rq, use_napi);	switch (ret) {	case NET_RX_SUCCESS: /* same as NETDEV_TX_OK */		if (!use_napi)			dev_sw_netstats_tx_add(dev, 1, length);		else			__veth_xdp_flush(rq);		break;	case NETDEV_TX_BUSY:		/* If a qdisc is attached to our virtual device, returning		 * NETDEV_TX_BUSY is allowed.		 */		txq = netdev_get_tx_queue(dev, rxq); 		if (qdisc_txq_has_no_queue(txq)) {			dev_kfree_skb_any(skb);			goto drop;		}		/* Restore Eth hdr pulled by dev_forward_skb/eth_type_trans */		__skb_push(skb, ETH_HLEN);		netif_tx_stop_queue(txq);		/* Makes sure NAPI peer consumer runs. Consumer is responsible		 * for starting txq again, until then ndo_start_xmit (this		 * function) will not be invoked by the netstack again.		 */		__veth_xdp_flush(rq);		break;	case NET_RX_DROP: /* same as NET_XMIT_DROP */drop:		atomic64_inc(&priv->dropped);		ret = NET_XMIT_DROP;		break;	default:		net_crit_ratelimited("%s(%s): Invalid return code(%d)",				     __func__, dev->name, ret);	}	rcu_read_unlock(); 	return ret;} static void veth_stats_rx(struct veth_stats *result, struct net_device *dev){	struct veth_priv *priv = netdev_priv(dev);	int i; 	result->peer_tq_xdp_xmit_err = 0;	result->xdp_packets = 0;	result->xdp_tx_err = 0;	result->xdp_bytes = 0;	result->rx_drops = 0;	for (i = 0; i < dev->num_rx_queues; i++) {		u64 packets, bytes, drops, xdp_tx_err, peer_tq_xdp_xmit_err;		struct veth_rq_stats *stats = &priv->rq[i].stats;		unsigned int start; 		do {			start = u64_stats_fetch_begin(&stats->syncp);			peer_tq_xdp_xmit_err = stats->vs.peer_tq_xdp_xmit_err;			xdp_tx_err = stats->vs.xdp_tx_err;			packets = stats->vs.xdp_packets;			bytes = stats->vs.xdp_bytes;			drops = stats->vs.rx_drops;		} while (u64_stats_fetch_retry(&stats->syncp, start));		result->peer_tq_xdp_xmit_err += peer_tq_xdp_xmit_err;		result->xdp_tx_err += xdp_tx_err;		result->xdp_packets += packets;		result->xdp_bytes += bytes;		result->rx_drops += drops;	}} static void veth_get_stats64(struct net_device *dev,			     struct rtnl_link_stats64 *tot){	struct veth_priv *priv = netdev_priv(dev);	struct net_device *peer;	struct veth_stats rx; 	tot->tx_dropped = atomic64_read(&priv->dropped);	dev_fetch_sw_netstats(tot, dev->tstats); 	veth_stats_rx(&rx, dev);	tot->tx_dropped += rx.xdp_tx_err;	tot->rx_dropped = rx.rx_drops + rx.peer_tq_xdp_xmit_err;	tot->rx_bytes += rx.xdp_bytes;	tot->rx_packets += rx.xdp_packets; 	rcu_read_lock();	peer = rcu_dereference(priv->peer);	if (peer) {		struct rtnl_link_stats64 tot_peer = {}; 		dev_fetch_sw_netstats(&tot_peer, peer->tstats);		tot->rx_bytes += tot_peer.tx_bytes;		tot->rx_packets += tot_peer.tx_packets; 		veth_stats_rx(&rx, peer);		tot->tx_dropped += rx.peer_tq_xdp_xmit_err;		tot->rx_dropped += rx.xdp_tx_err;		tot->tx_bytes += rx.xdp_bytes;		tot->tx_packets += rx.xdp_packets;	}	rcu_read_unlock();} /* fake multicast ability */static void veth_set_multicast_list(struct net_device *dev){} static int veth_select_rxq(struct net_device *dev){	return smp_processor_id() % dev->real_num_rx_queues;} static struct net_device *veth_peer_dev(struct net_device *dev){	struct veth_priv *priv = netdev_priv(dev); 	/* Callers must be under RCU read side. */

Lines 1–500 of 2026.