git clone https://git.yaroslavps.com/genltest
genltest.c
/* 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 */staticintecho_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 */staticintecho_ping(constchar *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.*/staticssize_tping_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);staticint __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;}staticvoid __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);
genltest.h
#ifndef GENLTEST_H#define GENLTEST_H/** This header includes definitions that are shared with kernel space and user* space. This header would be put in a place visible to user space.*/#define GENLTEST_GENL_NAME "genltest"#define GENLTEST_GENL_VERSION 1#define GENLTEST_MC_GRP_NAME "mcgrp"/* Attributes */enum genltest_attrs {GENLTEST_A_UNSPEC,GENLTEST_A_MSG,__GENLTEST_A_MAX,};#define GENLTEST_A_MAX (__GENLTEST_A_MAX - 1)/* Commands */enum genltest_cmds {GENLTEST_CMD_UNSPEC,GENLTEST_CMD_ECHO,__GENLTEST_CMD_MAX,};#define GENLTEST_CMD_MAX (__GENLTEST_CMD_MAX - 1)#endif
KERNELDIR := /lib/modules/$(shell uname -r)/build# CFLAGS_genltest.o := -O0obj-m += genltest.oall:$(MAKE) -C $(KERNELDIR) V=0 M=`pwd` modulesclean:#$(MAKE) -C $(KERNELDIR) M=`pwd` clean@rm -f *.ko *.o modules.order Module.symvers *.mod.? *~@rm -rf .tmp_versions .cache.mk *.ur-safe *.mod
genltest.c
/* SPDX-License-Identifier: GPL-2.0 *//** Generic Netlink libnl example program** An example on how to use the libnl library for communicating over Generic* Netlink with kernel modules, with unicast and multicast group messages.** Copyright (c) 2022 Yaroslav de la Peña Smirnov <yps@yaroslavps.com>*/#include<stdio.h>#include<errno.h>#include<signal.h>#include<stdbool.h>#include<netlink/socket.h>#include<netlink/netlink.h>#include<netlink/genl/ctrl.h>#include<netlink/genl/genl.h>#include<netlink/genl/family.h>#include"../ks/genltest.h"#define prerr(...) fprintf(stderr, "error: " __VA_ARGS__)/** libnl docs and API: https://www.infradead.org/~tgr/libnl/* Current libnl repo: https://github.com/thom311/libnl*//** Handler for all received messages from our Generic Netlink family, both* unicast and multicast.*/staticintecho_reply_handler(struct nl_msg *msg, void *arg){int err = 0;struct genlmsghdr *genlhdr = nlmsg_data(nlmsg_hdr(msg));struct nlattr *tb[GENLTEST_A_MAX + 1];/* Parse the attributes */err = nla_parse(tb, GENLTEST_A_MAX, genlmsg_attrdata(genlhdr, 0),genlmsg_attrlen(genlhdr, 0), NULL);if (err) {prerr("unable to parse message: %s\n", strerror(-err));return NL_SKIP;}/* Check that there's actually a payload */if (!tb[GENLTEST_A_MSG]) {prerr("msg attribute missing from message\n");return NL_SKIP;}/* Print it! */printf("message received: %s\n", nla_get_string(tb[GENLTEST_A_MSG]));return NL_OK;}/* Send (unicast) GENLTEST_CMD_ECHO request message */staticintsend_echo_msg(struct nl_sock *sk, int fam){int err = 0;struct nl_msg *msg = nlmsg_alloc();if (!msg) {return -ENOMEM;}/* Put the genl header inside message buffer */void *hdr = genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, fam, 0, 0,GENLTEST_CMD_ECHO, GENLTEST_GENL_VERSION);if (!hdr) {return -EMSGSIZE;}/* Put the string inside the message. */err = nla_put_string(msg, GENLTEST_A_MSG,"Hello from User Space, Netlink!");if (err < 0) {return -err;}printf("message sent\n");/* Send the message. */err = nl_send_auto(sk, msg);err = err >= 0 ? 0 : err;nlmsg_free(msg);return err;}/* Allocate netlink socket and connect to generic netlink */staticintconn(struct nl_sock **sk){*sk = nl_socket_alloc();if (!sk) {return -ENOMEM;}return genl_connect(*sk);}/* Disconnect and release socket */staticvoiddisconn(struct nl_sock *sk){nl_close(sk);nl_socket_free(sk);}/* Modify the callback for replies to handle all received messages */staticinlineintset_cb(struct nl_sock *sk){return nl_socket_modify_cb(sk, NL_CB_VALID, NL_CB_CUSTOM,echo_reply_handler, NULL);}intmain(void){int ret = 1;struct nl_sock *ucsk, *mcsk;/** We use one socket to receive asynchronous "notifications" over* multicast group, and another for ops. We do this so that we don't mix* up responses from ops with notifications to make handling easier.*/if ((ret = conn(&ucsk)) || (ret = conn(&mcsk))) {prerr("failed to connect to generic netlink\n");goto out;}/* Resolve the genl family. One family for both unicast and multicast. */int fam = genl_ctrl_resolve(ucsk, GENLTEST_GENL_NAME);if (fam < 0) {prerr("failed to resolve generic netlink family: %s\n",strerror(-fam));goto out;}/* Disable sequence checks for asynchronous multicast messages. */nl_socket_disable_seq_check(mcsk);/* Resolve the multicast group. */int mcgrp = genl_ctrl_resolve_grp(mcsk, GENLTEST_GENL_NAME,GENLTEST_MC_GRP_NAME);if (mcgrp < 0) {prerr("failed to resolve generic netlink multicast group: %s\n",strerror(-mcgrp));goto out;}/* Join the multicast group. */if ((ret = nl_socket_add_membership(mcsk, mcgrp) < 0)) {prerr("failed to join multicast group: %s\n", strerror(-ret));goto out;}if ((ret = set_cb(ucsk)) || (ret = set_cb(mcsk))) {prerr("failed to set callback: %s\n", strerror(-ret));goto out;}/* Send unicast message and listen for response. */if ((ret = send_echo_msg(ucsk, fam))) {prerr("failed to send message: %s\n", strerror(-ret));}printf("listening for messages\n");nl_recvmsgs_default(ucsk);/* Listen for "notifications". */while (1) {nl_recvmsgs_default(mcsk);}ret = 0;out:disconn(ucsk);disconn(mcsk);return ret;}
all: genltest.cgcc genltest.c $(shell pkg-config --cflags --libs libnl-3.0 libnl-genl-3.0) -o genltest
夜雨聆风