diff options
author | Yaroslav de la Peña Smirnov <yps@yaroslavps.com> | 2023-02-11 01:51:36 +0300 |
---|---|---|
committer | Yaroslav de la Peña Smirnov <yps@yaroslavps.com> | 2023-02-11 01:51:36 +0300 |
commit | 017598b41e59aa0bdb121020c29746cd062cdec0 (patch) | |
tree | ac4227f670146feae286fa9afb0d6dbd2f41dbea /ks/genltest.c | |
download | genltest-017598b41e59aa0bdb121020c29746cd062cdec0.tar.gz genltest-017598b41e59aa0bdb121020c29746cd062cdec0.zip |
Diffstat (limited to 'ks/genltest.c')
-rw-r--r-- | ks/genltest.c | 215 |
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); |