The loopback driver was introduced to readers in this article (here). We will now add an IOCTL interface to the loopback network device driver.
The netdevice operations structure net_device_ops provides a function registration for IOCTLs. In earlier versions of the Linux kernel, the OP callback in the net_device_ops structure was ndo_do_ioctl. However, this callback is now deprecated in recent kernels (since Linux 5.14 release). The net_device_ops callback that is now defined is ndo_siocdevprivate (since Linux 5.15 release).
The below code provides the ioctl interface function registration and a single ioctl to reset the statistics.
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/init.h>
#include <linux/uaccess.h>
#include <linux/sockios.h>
#define DRV_NAME "vivek_net"
MODULE_AUTHOR("Vivekananda Uppunda");
MODULE_DESCRIPTION("Virtual netdev with software buffer management");
MODULE_LICENSE("GPL");
#define VNET_IOCTL_RESET_STATS (SIOCDEVPRIVATE + 0)
/* -------------------------------------------------- */
/* Private data */
/* -------------------------------------------------- */
struct vnet_priv {
struct sk_buff_head tx_queue; /* software TX buffer */
};
/* -------------------------------------------------- */
/* Software RX processing (simple loopback) */
/* -------------------------------------------------- */
static void vnet_process_tx_queue(struct net_device *dev)
{
struct vnet_priv *priv = netdev_priv(dev);
struct sk_buff *skb;
while ((skb = skb_dequeue(&priv->tx_queue)) != NULL) {
/* Convert TX skb into RX skb */
skb->dev = dev;
skb->protocol = eth_type_trans(skb, dev);
skb->ip_summed = CHECKSUM_UNNECESSARY;
dev->stats.rx_packets++;
dev->stats.rx_bytes += skb->len;
netif_rx(skb);
}
}
/* -------------------------------------------------- */
/* IOCTL function */
/* -------------------------------------------------- */
static int vnet_siocdevprivate(struct net_device *dev,
struct ifreq *ifr,
void __user *data,
int cmd)
{
pr_info("vnet_siocdevprivate\n");
switch (cmd) {
case VNET_IOCTL_RESET_STATS:
memset(&dev->stats, 0, sizeof(dev->stats));
pr_info("%s: stats reset via ioctl\n", dev->name);
break;
default:
pr_info("%s: default not supported\n", dev->name);
return -EOPNOTSUPP;
}
return 0;
}
/* -------------------------------------------------- */
/* Transmit function */
/* -------------------------------------------------- */
static netdev_tx_t vnet_start_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct vnet_priv *priv = netdev_priv(dev);
dev->stats.tx_packets++;
dev->stats.tx_bytes += skb->len;
/* Queue packet into software buffer */
skb_queue_tail(&priv->tx_queue, skb);
/* Immediately process queued packets (simple model) */
vnet_process_tx_queue(dev);
return NETDEV_TX_OK;
}
/* -------------------------------------------------- */
/* Open / Close */
/* -------------------------------------------------- */
static int vnet_open(struct net_device *dev)
{
struct vnet_priv *priv = netdev_priv(dev);
skb_queue_head_init(&priv->tx_queue);
netif_start_queue(dev);
pr_info("%s: device opened\n", dev->name);
return 0;
}
static int vnet_stop(struct net_device *dev)
{
struct vnet_priv *priv = netdev_priv(dev);
struct sk_buff *skb;
netif_stop_queue(dev);
/* Flush software queue */
while ((skb = skb_dequeue(&priv->tx_queue)) != NULL)
dev_kfree_skb(skb);
pr_info("%s: device closed\n", dev->name);
return 0;
}
/* -------------------------------------------------- */
/* net_device operations */
/* -------------------------------------------------- */
static const struct net_device_ops vnet_netdev_ops = {
.ndo_open = vnet_open,
.ndo_stop = vnet_stop,
.ndo_start_xmit = vnet_start_xmit,
.ndo_siocdevprivate = vnet_siocdevprivate,
};
/* -------------------------------------------------- */
/* Device setup */
/* -------------------------------------------------- */
static void vnet_setup(struct net_device *dev)
{
ether_setup(dev);
dev->netdev_ops = &vnet_netdev_ops;
dev->flags |= IFF_NOARP;
dev->features |= NETIF_F_HW_CSUM;
eth_hw_addr_random(dev);
}
/* -------------------------------------------------- */
/* Module init / exit */
/* -------------------------------------------------- */
static struct net_device *vnet_dev;
static int __init vnet_init(void)
{
int ret;
vnet_dev = alloc_netdev(sizeof(struct vnet_priv),
DRV_NAME"%d",
NET_NAME_UNKNOWN,
vnet_setup);
if (!vnet_dev)
return -ENOMEM;
ret = register_netdev(vnet_dev);
if (ret) {
free_netdev(vnet_dev);
return ret;
}
pr_info(DRV_NAME ": virtual network device registered\n");
return 0;
}
static void __exit vnet_exit(void)
{
unregister_netdev(vnet_dev);
free_netdev(vnet_dev);
pr_info(DRV_NAME ": module unloaded\n");
}
module_init(vnet_init);
module_exit(vnet_exit);
The above code does the following.
- Registers a loopback networking device
- Registers an ioctl callback in net_device_ops structure vnet_siocdevprivate
- The IOCTL created is VNET_IOCTL_RESET_STATS
- It is used to reset the statistics for the network device
We will write a sample program which sends a reset command to the virtual loopback network driver in the next article and discuss the functioning of the same.
Sample application to send IOCTL to virtual loopback networking driver