In the previous article (placed here), we incorporated multiple networking transmit and receive queues for the loopback networking device driver that we are developing. In this article, we will introduce the Netlink interface to the driver. We will incorporate a Generic Netlink family and register it with the Generic Netlink sub-system. The interested reader can refer the series on Netlink starting with the article link provided here.
We will also develop a sample application that invokes the Netlink command in the following article after this.
#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>
#include <net/genetlink.h>
#define DRV_NAME "vivek_net"
MODULE_AUTHOR("Vivekananda Uppunda");
MODULE_DESCRIPTION("Virtual netdev with TX/RX queues for network stack");
MODULE_LICENSE("GPL");
#define VNET_IOCTL_RESET_STATS (SIOCDEVPRIVATE + 0)
#define NETLINK_FAMILY_NAME "vnet_nl"
#define NETLINK_VERSION 1
#define VNET_TX_QUEUES 4
#define VNET_RX_QUEUES 2
spinlock_t priv_lock;
struct vnet_priv {
struct sk_buff_head tx_ring[VNET_TX_QUEUES]; /* driver-level TX queues */
struct sk_buff_head rx_ring[VNET_RX_QUEUES]; /* driver-level RX queues */
};
/* =========================
* Attributes
* ========================= */
enum {
NL_A_UNSPEC,
NL_A_MSG, /* string */
NL_A_ID, /* u32 */
NL_A_FLAG, /* u8 */
__NL_A_MAX,
};
#define NL_A_MAX (__NL_A_MAX - 1)
/* =========================
* Commands
* ========================= */
enum {
NL_CMD_UNSPEC,
NL_CMD_ECHO,
__NL_CMD_MAX,
};
#define NL_CMD_MAX (__NL_CMD_MAX - 1)
/* =========================
* Policy
* ========================= */
static const struct nla_policy netlink_policy[NL_A_MAX + 1] = {
[NL_A_MSG] = { .type = NLA_NUL_STRING, .len = 256 },
};
/* =========================
* Multicast group
* ========================= */
enum {
NL_MCGRP_EVENTS,
};
static const struct genl_multicast_group netlink_mcgrps[] = {
[NL_MCGRP_EVENTS] = { .name = "events" },
};
static int netlink_echo(struct sk_buff *skbuff, struct genl_info *info);
/* =========================
* Operations
* ========================= */
static const struct genl_ops netlink_ops[] = {
{
.cmd = NL_CMD_ECHO,
.flags = 0,
.policy = netlink_policy,
.doit = netlink_echo,
},
};
/* =========================
* Family
* ========================= */
static struct genl_family netlink_family = {
.name = NETLINK_FAMILY_NAME,
.version = NETLINK_VERSION,
.maxattr = NL_A_MAX,
.ops = netlink_ops,
.n_ops = ARRAY_SIZE(netlink_ops),
.mcgrps = netlink_mcgrps,
.n_mcgrps = ARRAY_SIZE(netlink_mcgrps),
.module = THIS_MODULE,
};
/* =========================
* DOIT handler
* ========================= */
static int netlink_echo(struct sk_buff *skbuff, struct genl_info *info)
{
struct sk_buff *msg;
void *hdr;
const char *user_msg;
int ret;
spin_lock(&priv_lock);
if (!info->attrs[NL_A_MSG]) {
spin_unlock(&priv_lock);
return -EINVAL;
}
user_msg = nla_data(info->attrs[NL_A_MSG]);
pr_info("message received: %s\n", user_msg);
/* Allocate reply */
msg = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
if (!msg) {
spin_unlock(&priv_lock);
return -ENOMEM;
}
hdr = genlmsg_put_reply(msg, info, &netlink_family, 0, NL_CMD_ECHO);
if (!hdr) {
nlmsg_free(msg);
spin_unlock(&priv_lock);
return -ENOMEM;
}
ret = nla_put_string(msg, NL_A_MSG, user_msg);
if (ret) {
nlmsg_free(msg);
spin_unlock(&priv_lock);
return ret;
}
genlmsg_end(msg, hdr);
/* Unicast reply */
ret = genlmsg_reply(msg, info);
if (ret)
pr_err("reply failed\n");
/* Optional multicast broadcast in case it needs to be broadcast */
/* genlmsg_multicast(&netlink_family, msg, 0, NL_MCGRP_EVENTS, GFP_KERNEL);*/
spin_unlock(&priv_lock);
return ret;
}
/* -------------------------------------------------- */
/* IOCTL function */
/* -------------------------------------------------- */
static int vnet_siocdevprivate(struct net_device *dev,
struct ifreq *ifr,
void __user *data,
int cmd)
{
pr_info("vnet_siocdevprivate\n");
spin_lock(&priv_lock);
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;
}
spin_unlock(&priv_lock);
return 0;
}
static void vnet_process_rx_ring(struct net_device *dev)
{
struct vnet_priv *priv = netdev_priv(dev);
struct sk_buff *skb;
int i;
for (i = 0; i < VNET_RX_QUEUES; i++) {
while ((skb = skb_dequeue(&priv->rx_ring[i])) != NULL) {
/* Inject into networking stack */
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;
pr_info("%s: RX queue %d delivering packet (len=%u)\n",
dev->name, i, skb->len);
netif_rx(skb);
}
}
}
static netdev_tx_t vnet_start_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct vnet_priv *priv = netdev_priv(dev);
unsigned int q = skb_get_queue_mapping(skb);
struct sk_buff *copy;
if (q >= VNET_TX_QUEUES)
q = 0;
spin_lock(&priv_lock);
dev->stats.tx_packets++;
dev->stats.tx_bytes += skb->len;
pr_info("%s: TX queue %d enqueue packet (len=%u)\n",
dev->name, q, skb->len);
/* Copy skb into driver-owned buffer (simulate DMA mapping) */
copy = skb_copy(skb, GFP_ATOMIC);
if (copy) {
unsigned int rx_q = q % VNET_RX_QUEUES;
skb_queue_tail(&priv->rx_ring[rx_q], copy);
pr_info("%s: copied to RX queue %d\n", dev->name, rx_q);
}
/* Free original skb (stack no longer owns it) */
dev_kfree_skb(skb);
/* Process RX ring immediately (loopback model) */
vnet_process_rx_ring(dev);
spin_unlock(&priv_lock);
return NETDEV_TX_OK;
}
static int vnet_open(struct net_device *dev)
{
struct vnet_priv *priv = netdev_priv(dev);
int i;
for (i = 0; i < VNET_TX_QUEUES; i++)
skb_queue_head_init(&priv->tx_ring[i]);
for (i = 0; i < VNET_RX_QUEUES; i++)
skb_queue_head_init(&priv->rx_ring[i]);
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;
int i;
netif_stop_queue(dev);
for (i = 0; i < VNET_TX_QUEUES; i++)
while ((skb = skb_dequeue(&priv->tx_ring[i])) != NULL)
dev_kfree_skb(skb);
for (i = 0; i < VNET_RX_QUEUES; i++)
while ((skb = skb_dequeue(&priv->rx_ring[i])) != NULL)
dev_kfree_skb(skb);
pr_info("%s: device closed\n", dev->name);
return 0;
}
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,
};
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);
dev->num_tx_queues = VNET_TX_QUEUES;
dev->num_rx_queues = VNET_RX_QUEUES;
}
static struct net_device *vnet_dev;
static int __init vnet_init(void)
{
int ret;
vnet_dev = alloc_netdev_mqs(sizeof(struct vnet_priv),
DRV_NAME"%d",
NET_NAME_UNKNOWN,
vnet_setup,
VNET_TX_QUEUES,
VNET_RX_QUEUES);
if (!vnet_dev)
return -ENOMEM;
ret = register_netdev(vnet_dev);
if (ret) {
free_netdev(vnet_dev);
return ret;
}
ret = genl_register_family(&netlink_family);
if (ret) {
unregister_netdev(vnet_dev);
free_netdev(vnet_dev);
return ret;
}
spin_lock_init(&priv_lock);
pr_info(DRV_NAME ": registered with %d TX and %d RX queues\n",
VNET_TX_QUEUES, VNET_RX_QUEUES);
return 0;
}
static void __exit vnet_exit(void)
{
unregister_netdev(vnet_dev);
genl_unregister_family(&netlink_family);
free_netdev(vnet_dev);
pr_info(DRV_NAME ": module unloaded\n");
}
module_init(vnet_init);
module_exit(vnet_exit);
The above code for netlink specifically performs the following.
- Creates a generic netlink family whose name is vnet_nl and registers it
- A netlink command NL_CMD_ECHO is created to echo the received netlink message from userspace.
- The policy and attributes supported by the command are also defined
- When User space sends a message to the vnet_nl Generic netlink family using the command NL_CMD_ECHO
- It formulates an echo back to the user space application and the user space application should receive it
In the following articles, we will discuss certain aspects of the netlink code and also develop a sample application which will send the NL_CMD_ECHO netlink command to the device driver.