aboutsummaryrefslogtreecommitdiff
path: root/ks/genltest.c
diff options
context:
space:
mode:
Diffstat (limited to 'ks/genltest.c')
-rw-r--r--ks/genltest.c215
1 files changed, 215 insertions, 0 deletions
diff --git a/ks/genltest.c b/ks/genltest.c
new file mode 100644
index 0000000..fa9a960
--- /dev/null
+++ b/ks/genltest.c
@@ -0,0 +1,215 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Generic Netlink example module
+ *
+ * An example on how to use the Generic Netlink API with ops and multicast
+ * groups.
+ *
+ * Copyright (c) 2022 Yaroslav de la Peña Smirnov <yps@yaroslavps.com>
+ */
+#define pr_fmt(fmt) "genltest: " fmt
+
+#include "genltest.h"
+
+#include <linux/module.h>
+#include <net/genetlink.h>
+
+/*
+ * Some materials that were used as source for this example/tutorial/guide:
+ *
+ * - https://wiki.linuxfoundation.org/networking/generic_netlink_howto
+ * - https://kernel.org/doc/html/next/userspace-api/netlink/intro.html
+ */
+
+#define MSG_MAX_LEN 1024
+
+/* Forward declaration */
+static struct genl_family genl_fam;
+
+/* Handler for GENLTEST_CMD_ECHO messages received */
+static int echo_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ int ret = 0;
+ void *hdr;
+ struct sk_buff *msg;
+
+ /* Check if the attribute is present and print it */
+ if (info->attrs[GENLTEST_A_MSG]) {
+ char *str = nla_data(info->attrs[GENLTEST_A_MSG]);
+ pr_info("message received: %s\n", str);
+ } else {
+ pr_info("empty message received\n");
+ }
+
+ /* Allocate a new buffer for the reply */
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg) {
+ pr_err("failed to allocate message buffer\n");
+ return -ENOMEM;
+ }
+
+ /* Put the Generic Netlink header */
+ hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq, &genl_fam, 0,
+ GENLTEST_CMD_ECHO);
+ if (!hdr) {
+ pr_err("failed to create genetlink header\n");
+ nlmsg_free(msg);
+ return -EMSGSIZE;
+ }
+ /* And the message */
+ if ((ret = nla_put_string(msg, GENLTEST_A_MSG,
+ "Hello from Kernel Space, Netlink!"))) {
+ pr_err("failed to create message string\n");
+ genlmsg_cancel(msg, hdr);
+ nlmsg_free(msg);
+ goto out;
+ }
+
+ /* Finalize the message and send it */
+ genlmsg_end(msg, hdr);
+
+ ret = genlmsg_reply(msg, info);
+ pr_info("reply sent\n");
+
+out:
+ return ret;
+}
+
+/* Attribute validation policy for our echo command */
+static struct nla_policy echo_pol[GENLTEST_A_MAX + 1] = {
+ [GENLTEST_A_MSG] = { .type = NLA_NUL_STRING },
+};
+
+/* Operations for our Generic Netlink family */
+static struct genl_ops genl_ops[] = {
+ {
+ .cmd = GENLTEST_CMD_ECHO,
+ .policy = echo_pol,
+ .doit = echo_doit,
+ },
+};
+
+/* Multicast groups for our family */
+static const struct genl_multicast_group genl_mcgrps[] = {
+ { .name = GENLTEST_MC_GRP_NAME },
+};
+
+/* Generic Netlink family */
+static struct genl_family genl_fam = {
+ .name = GENLTEST_GENL_NAME,
+ .version = GENLTEST_GENL_VERSION,
+ .maxattr = GENLTEST_A_MAX,
+ .ops = genl_ops,
+ .n_ops = ARRAY_SIZE(genl_ops),
+ .mcgrps = genl_mcgrps,
+ .n_mcgrps = ARRAY_SIZE(genl_mcgrps),
+};
+
+/* Multicast ping message to our genl multicast group */
+static int echo_ping(const char *buf, size_t cnt)
+{
+ int ret = 0;
+ void *hdr;
+ /* Allocate message buffer */
+ struct sk_buff *skb = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+
+ if (unlikely(!skb)) {
+ pr_err("failed to allocate memory for genl message\n");
+ return -ENOMEM;
+ }
+
+ /* Put the Generic Netlink header */
+ hdr = genlmsg_put(skb, 0, 0, &genl_fam, 0, GENLTEST_CMD_ECHO);
+ if (unlikely(!hdr)) {
+ pr_err("failed to allocate memory for genl header\n");
+ nlmsg_free(skb);
+ return -ENOMEM;
+ }
+
+ /* And the message */
+ if ((ret = nla_put_string(skb, GENLTEST_A_MSG, buf))) {
+ pr_err("unable to create message string\n");
+ genlmsg_cancel(skb, hdr);
+ nlmsg_free(skb);
+ return ret;
+ }
+
+ /* Finalize the message */
+ genlmsg_end(skb, hdr);
+
+ /* Send it over multicast to the 0-th mc group in our array. */
+ ret = genlmsg_multicast(&genl_fam, skb, 0, 0, GFP_KERNEL);
+ if (ret == -ESRCH) {
+ pr_warn("multicast message sent, but nobody was listening...\n");
+ } else if (ret) {
+ pr_err("failed to send multicast genl message\n");
+ } else {
+ pr_info("multicast message sent\n");
+ }
+
+ return ret;
+}
+
+/*
+ * Test sysfs attr to send multicast messages. The string in the buffer will be
+ * echoed to the multicast group.
+ */
+static ssize_t ping_store(struct kobject *kobj, struct kobj_attribute *attr,
+ const char *buf, size_t cnt)
+{
+ int max = cnt > MSG_MAX_LEN ? MSG_MAX_LEN : cnt;
+ echo_ping(buf, max);
+
+ return max;
+}
+
+static struct kobject *kobj;
+static struct kobj_attribute ping_attr = __ATTR_WO(ping);
+
+static int __init init_genltest(void)
+{
+ int ret = 0;
+
+ pr_info("init start\n");
+
+ kobj = kobject_create_and_add("genltest", kobj);
+ if (unlikely(!kobj)) {
+ pr_err("unable to create kobject\n");
+ return -ENOMEM;
+ }
+ ret = sysfs_create_file(kobj, &ping_attr.attr);
+ if (unlikely(ret)) {
+ pr_err("unable to create sysfs file\n");
+ kobject_put(kobj);
+ return ret;
+ }
+
+ ret = genl_register_family(&genl_fam);
+ if (unlikely(ret)) {
+ pr_crit("failed to register generic netlink family\n");
+ sysfs_remove_file(kobj, &ping_attr.attr);
+ kobject_put(kobj);
+ }
+
+ pr_info("init end\n");
+
+ return ret;
+}
+
+static void __exit exit_genltest(void)
+{
+ if (unlikely(genl_unregister_family(&genl_fam))) {
+ pr_err("failed to unregister generic netlink family\n");
+ }
+
+ sysfs_remove_file(kobj, &ping_attr.attr);
+ kobject_put(kobj);
+
+ pr_info("exit\n");
+}
+
+MODULE_AUTHOR("Yaroslav de la Peña Smirnov");
+MODULE_LICENSE("GPL");
+
+module_init(init_genltest);
+module_exit(exit_genltest);