If you want to use netlink as a userspace-kernelspace interface for your own non-networking custom use, make sure to go the Generic Netlink path - get a family id assigned and then used that to exchange messages between your userspace and kernelspace. That said, its better to just use Netlink Protocol Library Suite (libnl). If for some extremely compelling reason you can't, use libnl, here is some sample code to get you started.
Its based on Ariane Kellar's code from here. I have simplified and commented the userspace side code a lot. The kernel space code is mostly unchanged. The kernel side code required a minor change in genlmsg_unicast() call to ensure compatibility with the newer kernel versions.
nl_kern.c :
nl_user.c :
Makefile :
Keep the above source files organized in the folders like this if you want to use the above Makefile:
Its based on Ariane Kellar's code from here. I have simplified and commented the userspace side code a lot. The kernel space code is mostly unchanged. The kernel side code required a minor change in genlmsg_unicast() call to ensure compatibility with the newer kernel versions.
nl_kern.c :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | #include <net/genetlink.h> #include <linux/module.h> #include <linux/kernel.h> //Code based on http://people.ee.ethz.ch/~arkeller/linux/multi/kernel_user_space_howto-3.html /* attributes (variables): * the index in this enum is used as a reference for the type, * userspace application has to indicate the corresponding type * the policy is used for security considerations */ enum { DOC_EXMPL_A_UNSPEC, DOC_EXMPL_A_MSG, __DOC_EXMPL_A_MAX, }; #define DOC_EXMPL_A_MAX (__DOC_EXMPL_A_MAX - 1) /* attribute policy: defines which attribute has which type (e.g int, char * etc) * possible values defined in net/netlink.h */ static struct nla_policy doc_exmpl_genl_policy[DOC_EXMPL_A_MAX + 1] = { [DOC_EXMPL_A_MSG] = { .type = NLA_NUL_STRING }, }; #define VERSION_NR 1 //family definition static struct genl_family doc_exmpl_gnl_family = { .id = GENL_ID_GENERATE, //Genetlink should generate an id .hdrsize = 0, .name = "CONTROL_EXMPL", //The name of this family, used by userspace application .version = VERSION_NR, //Version number .maxattr = DOC_EXMPL_A_MAX, }; /* commands: enumeration of all commands (functions), * used by userspace application to identify command to be executed */ enum { DOC_EXMPL_C_UNSPEC, DOC_EXMPL_C_ECHO, __DOC_EXMPL_C_MAX, }; #define DOC_EXMPL_C_MAX (__DOC_EXMPL_C_MAX - 1) //An echo command, receives a message, prints it and sends another message back int doc_exmpl_echo(struct sk_buff *skb_2, struct genl_info *info) { struct nlattr *na; struct sk_buff *skb; int rc; void *msg_head; char * mydata; if (info == NULL) { goto out; } /* For each attribute there is an index in info->attrs which points to a nlattr structure * in this structure the data is given */ na = info->attrs[DOC_EXMPL_A_MSG]; if (na) { mydata = (char *)nla_data(na); if (mydata == NULL) { printk("error while receiving data\n"); } else { printk("received: %s\n", mydata); } } else { printk("no info->attrs %i\n", DOC_EXMPL_A_MSG); } //Send a message back //Allocate some memory, since the size is not yet known use NLMSG_GOODSIZE skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); if (skb == NULL) { goto out; } //Create the message headers /* arguments of genlmsg_put: struct sk_buff *, int (sending) pid, int sequence number, struct genl_family *, int flags, u8 command index (why do we need this?) */ msg_head = genlmsg_put(skb, 0, info->snd_seq+1, &doc_exmpl_gnl_family, 0, DOC_EXMPL_C_ECHO); if (msg_head == NULL) { rc = -ENOMEM; goto out; } //Add a DOC_EXMPL_A_MSG attribute (actual value to be sent) rc = nla_put_string(skb, DOC_EXMPL_A_MSG, "Hello World from kernel space"); if (rc != 0) { goto out; } //Finalize the message genlmsg_end(skb, msg_head); //Send the message back rc = genlmsg_unicast(genl_info_net(info), skb,info->snd_pid ); if (rc != 0) { goto out; } return 0; out: printk("An error occured in doc_exmpl_echo:\n"); return 0; } //Commands: mapping between the command enumeration and the actual function struct genl_ops doc_exmpl_gnl_ops_echo = { .cmd = DOC_EXMPL_C_ECHO, .flags = 0, .policy = doc_exmpl_genl_policy, .doit = doc_exmpl_echo, .dumpit = NULL, }; static int __init gnKernel_init(void) { int rc; printk("Generic Netlink Example Module inserted.\n"); //Register the new family rc = genl_register_family(&doc_exmpl_gnl_family); if (rc != 0) { goto failure; } //Register functions (commands) of the new family rc = genl_register_ops(&doc_exmpl_gnl_family, &doc_exmpl_gnl_ops_echo); if (rc != 0) { printk("Register ops: %i\n",rc); genl_unregister_family(&doc_exmpl_gnl_family); goto failure; } return 0; failure: printk("An error occured while inserting the generic netlink example module\n"); return -1; } static void __exit gnKernel_exit(void) { int ret; printk("Generic Netlink Example Module unloaded.\n"); //Unregister the functions ret = genl_unregister_ops(&doc_exmpl_gnl_family, &doc_exmpl_gnl_ops_echo); if(ret != 0) { printk("Unregister ops: %i\n",ret); } //Unregister the family ret = genl_unregister_family(&doc_exmpl_gnl_family); if(ret !=0) { printk("Unregister family %i\n",ret); return; } } module_init(gnKernel_init); module_exit(gnKernel_exit); MODULE_LICENSE("GPL"); |
nl_user.c :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <poll.h> #include <string.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/socket.h> #include <sys/types.h> #include <signal.h> #include <linux/genetlink.h> //Code based on http://people.ee.ethz.ch/~arkeller/linux/multi/kernel_user_space_howto-3.html /* Generic macros for dealing with netlink sockets. Might be duplicated * elsewhere. It is recommended that commercial grade applications use * libnl or libnetlink and use the interfaces provided by the library */ #define GENLMSG_DATA(glh) ((void *)(NLMSG_DATA(glh) + GENL_HDRLEN)) #define GENLMSG_PAYLOAD(glh) (NLMSG_PAYLOAD(glh, 0) - GENL_HDRLEN) #define NLA_DATA(na) ((void *)((char*)(na) + NLA_HDRLEN)) #define MESSAGE_TO_KERNEL "Hello World!" //Variables used for netlink int nl_fd; //netlink socket's file descriptor struct sockaddr_nl nl_address; //netlink socket address int nl_family_id; //The family ID resolved by the netlink controller for this userspace program int nl_rxtx_length; //Number of bytes sent or received via send() or recv() struct nlattr *nl_na; //pointer to netlink attributes structure within the payload struct { //memory for netlink request and response messages - headers are included struct nlmsghdr n; struct genlmsghdr g; char buf[256]; } nl_request_msg, nl_response_msg; int main(void) { //Step 1: Open the socket. Note that protocol = NETLINK_GENERIC nl_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC); if (nl_fd < 0) { perror("socket()"); return -1; } //Step 2: Bind the socket. memset(&nl_address, 0, sizeof(nl_address)); nl_address.nl_family = AF_NETLINK; nl_address.nl_groups = 0; if (bind(nl_fd, (struct sockaddr *) &nl_address, sizeof(nl_address)) < 0) { perror("bind()"); close(nl_fd); return -1; } //Step 3. Resolve the family ID corresponding to the string "CONTROL_EXMPL" //Populate the netlink header nl_request_msg.n.nlmsg_type = GENL_ID_CTRL; nl_request_msg.n.nlmsg_flags = NLM_F_REQUEST; nl_request_msg.n.nlmsg_seq = 0; nl_request_msg.n.nlmsg_pid = getpid(); nl_request_msg.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); //Populate the payload's "family header" : which in our case is genlmsghdr nl_request_msg.g.cmd = CTRL_CMD_GETFAMILY; nl_request_msg.g.version = 0x1; //Populate the payload's "netlink attributes" nl_na = (struct nlattr *) GENLMSG_DATA(&nl_request_msg); nl_na->nla_type = CTRL_ATTR_FAMILY_NAME; nl_na->nla_len = strlen("CONTROL_EXMPL") + 1 + NLA_HDRLEN; strcpy(NLA_DATA(nl_na), "CONTROL_EXMPL"); //Family name length can be upto 16 chars including \0 nl_request_msg.n.nlmsg_len += NLMSG_ALIGN(nl_na->nla_len); memset(&nl_address, 0, sizeof(nl_address)); nl_address.nl_family = AF_NETLINK; //Send the family ID request message to the netlink controller nl_rxtx_length = sendto(nl_fd, (char *) &nl_request_msg, nl_request_msg.n.nlmsg_len, 0, (struct sockaddr *) &nl_address, sizeof(nl_address)); if (nl_rxtx_length != nl_request_msg.n.nlmsg_len) { perror("sendto()"); close(nl_fd); return -1; } //Wait for the response message nl_rxtx_length = recv(nl_fd, &nl_response_msg, sizeof(nl_response_msg), 0); if (nl_rxtx_length < 0) { perror("recv()"); return -1; } //Validate response message if (!NLMSG_OK((&nl_response_msg.n), nl_rxtx_length)) { fprintf(stderr, "family ID request : invalid message\n"); return -1; } if (nl_response_msg.n.nlmsg_type == NLMSG_ERROR) { //error fprintf(stderr, "family ID request : receive error\n"); return -1; } //Extract family ID nl_na = (struct nlattr *) GENLMSG_DATA(&nl_response_msg); nl_na = (struct nlattr *) ((char *) nl_na + NLA_ALIGN(nl_na->nla_len)); if (nl_na->nla_type == CTRL_ATTR_FAMILY_ID) { nl_family_id = *(__u16 *) NLA_DATA(nl_na); } //Step 4. Send own custom message memset(&nl_request_msg, 0, sizeof(nl_request_msg)); memset(&nl_response_msg, 0, sizeof(nl_response_msg)); nl_request_msg.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); nl_request_msg.n.nlmsg_type = nl_family_id; nl_request_msg.n.nlmsg_flags = NLM_F_REQUEST; nl_request_msg.n.nlmsg_seq = 60; nl_request_msg.n.nlmsg_pid = getpid(); nl_request_msg.g.cmd = 1; //corresponds to DOC_EXMPL_C_ECHO; nl_na = (struct nlattr *) GENLMSG_DATA(&nl_request_msg); nl_na->nla_type = 1; // corresponds to DOC_EXMPL_A_MSG nl_na->nla_len = sizeof(MESSAGE_TO_KERNEL)+NLA_HDRLEN; //Message length memcpy(NLA_DATA(nl_na), MESSAGE_TO_KERNEL, sizeof(MESSAGE_TO_KERNEL)); nl_request_msg.n.nlmsg_len += NLMSG_ALIGN(nl_na->nla_len); memset(&nl_address, 0, sizeof(nl_address)); nl_address.nl_family = AF_NETLINK; //Send the custom message nl_rxtx_length = sendto(nl_fd, (char *) &nl_request_msg, nl_request_msg.n.nlmsg_len, 0, (struct sockaddr *) &nl_address, sizeof(nl_address)); if (nl_rxtx_length != nl_request_msg.n.nlmsg_len) { perror("sendto()"); close(nl_fd); return -1; } printf("Sent to kernel: %s\n",MESSAGE_TO_KERNEL); //Receive reply from kernel nl_rxtx_length = recv(nl_fd, &nl_response_msg, sizeof(nl_response_msg), 0); if (nl_rxtx_length < 0) { perror("recv()"); return -1; } //Validate response message if (nl_response_msg.n.nlmsg_type == NLMSG_ERROR) { //Error printf("Error while receiving reply from kernel: NACK Received\n"); close(nl_fd); return -1; } if (nl_rxtx_length < 0) { printf("Error while receiving reply from kernel\n"); close(nl_fd); return -1; } if (!NLMSG_OK((&nl_response_msg.n), nl_rxtx_length)) { printf("Error while receiving reply from kernel: Invalid Message\n"); close(nl_fd); return -1; } //Parse the reply message nl_rxtx_length = GENLMSG_PAYLOAD(&nl_response_msg.n); nl_na = (struct nlattr *) GENLMSG_DATA(&nl_response_msg); printf("Kernel replied: %s\n",(char *)NLA_DATA(nl_na)); //Step 5. Close the socket and quit close(nl_fd); return 0; } |
Makefile :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | obj-m += nl_kern.o nl_kern-objs := kern/nl_kern.o all: kernel-module-uninstall kernel-clean-ring-buffer kernel-build kernel-clean-temporary kernel-module-install user-build @tput setaf 3 @echo " done: all" @tput sgr0 clean: kernel-module-uninstall kernel-clean user-clean @tput setaf 3 @echo " done: clean" @tput sgr0 kernel-build: @tput setaf 1 @echo " kernel-build" @tput sgr0 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules kernel-clean: @tput setaf 1 @echo " kernel-clean" @tput sgr0 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean kernel-clean-temporary: @tput setaf 1 @echo " kernel-clean-temporary" @tput sgr0 -rm -rf *.o *~ core .depend .*.cmd *.mod.c .tmp_versions -rm -rf kern/*.o kern/*~ kern/core kern/.depend kern/.*.cmd kern/*.mod.c kern/.tmp_versions -rm -rf Module.symvers modules.order kernel-module-install: @tput setaf 1 @echo " kernel-module-install" @tput sgr0 -sudo insmod nl_kern.ko kernel-module-uninstall: @tput setaf 1 @echo " kernel-module-uninstall" @tput sgr0 -sudo rmmod nl_kern kernel-clean-ring-buffer: @tput setaf 1 @echo " kernel-clean-ring-buffer" @tput sgr0 sudo dmesg -c > /dev/null user-build: @tput setaf 1 @echo " user-build" @tput sgr0 gcc user/nl_user.c -o nl_user.out user-clean: @tput setaf 1 @echo " user-clean" @tput sgr0 rm -rf *.out |
Great work! Got this example up and running in no time. Here's some info for anyone that comes across it:
ReplyDelete[root@centos nl]# cat /etc/centos-release
CentOS release 6.5 (Final)
[root@centos nl]# uname -r
2.6.32-431.el6.x86_64
[root@centos nl]# pwd
/root/Desktop/nl
[root@centos nl]# ll -R
.:
total 480
drwxr-xr-x. 2 root root 4096 Apr 5 04:25 kern
-rw-r--r--. 1 root root 1389 Apr 5 04:13 Makefile
-rw-r--r--. 1 root root 232070 Apr 5 04:25 nl_kern.ko
-rw-r--r--. 1 root root 232070 Apr 5 04:25 nl_kern.ko.unsigned
-rwxr-xr-x. 1 root root 9880 Apr 5 04:25 nl_user.out
drwxr-xr-x. 2 root root 4096 Apr 5 04:12 user
./kern:
total 8
-rw-r--r--. 1 root root 4392 Apr 5 04:21 nl_kern.c
./user:
total 8
-rw-r--r--. 1 root root 6186 Apr 5 04:12 nl_user.c
Oh, forgot the most important part, it working!
ReplyDelete[root@centos nl]# pwd
/root/Desktop/nl
[root@centos nl]# ls
kern Makefile user
[root@centos nl]# make
kernel-module-uninstall
sudo rmmod nl_kern
ERROR: Module nl_kern does not exist in /proc/modules
make: [kernel-module-uninstall] Error 1 (ignored)
kernel-clean-ring-buffer
sudo dmesg -c > /dev/null
kernel-build
make -C /lib/modules/2.6.32-431.el6.x86_64/build M=/root/Desktop/nl modules
make[1]: Entering directory `/usr/src/kernels/2.6.32-431.11.2.el6.x86_64'
CC [M] /root/Desktop/nl/kern/nl_kern.o
LD [M] /root/Desktop/nl/nl_kern.o
Building modules, stage 2.
MODPOST 1 modules
CC /root/Desktop/nl/nl_kern.mod.o
LD [M] /root/Desktop/nl/nl_kern.ko.unsigned
NO SIGN [M] /root/Desktop/nl/nl_kern.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.32-431.11.2.el6.x86_64'
kernel-clean-temporary
rm -rf *.o *~ core .depend .*.cmd *.mod.c .tmp_versions
rm -rf kern/*.o kern/*~ kern/core kern/.depend kern/.*.cmd kern/*.mod.c kern/.tmp_versions
rm -rf Module.symvers modules.order
kernel-module-install
sudo insmod nl_kern.ko
user-build
gcc user/nl_user.c -o nl_user.out
done: all
[root@centos nl]# ls
kern Makefile nl_kern.ko nl_kern.ko.unsigned nl_user.out user
[root@centos nl]# ./nl_user.out
Sent to kernel: Hello World!
Kernel replied: Hello World from kernel space
Great job on the article, and thanks for fixing that bug! Found the other article before yours and I was wondering if I was doing something wrong
Now to get to hacking
Nice work! Just wondering, starting from line 152 in the kernel module:
ReplyDeleteIs it really necessary to call genl_unregister_ops() here? I thought genl_unregister_family() will unregisters all operations automatically anyway (see: http://lxr.free-electrons.com/source/net/netlink/genetlink.c?v=3.1#L324) And, why do you return if genl_unregister_ops() fails; then the family won't be unregistered, or am I wrong?
I know, this part of the code hasn't changed from Ariane Kellar's example, but I am curious and maybe you have an answer :)
Hey Jan,
ReplyDeleteIts has been just short of 2 years since I wrote up that code. I have since been engaged as a Teach For India fellow (similar to Teach For America) and have lost touch as to why I might have done that - I cant seem to re-collect anything at all!
I wrote this code for earlier kernel version - 2.4.x or 2.6.x. You seem to be referring to 3.1 - the netlink functions might have changed since then.
But as I look through the kernel code, I notice that an event is generated when you call genl_unregister_ops():
genl_ctrl_event(CTRL_CMD_DELOPS, ops);
And a different event is generated when the family is unregistered:
genl_ctrl_event(CTRL_CMD_DELFAMILY, family);
May be some depended code (even our own code which might make use of those events) expect those events to be provided to them in that sequence, so always better to call the functions in order.
Thanks for pointing out the bug regarding return being in the wrong place. I moved the return to after when genl_unregister_family() fails
how to pass multiple attributes at a time from user space to kernel?
ReplyDelete