Friday, February 21, 2014

Generic Netlink sockets - example code

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 :

  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

Keep the above source files organized in the folders like this if you want to use the above Makefile:

4 comments:

Rob R said...

Great work! Got this example up and running in no time. Here's some info for anyone that comes across it:

[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

Rob R said...

Oh, forgot the most important part, it working!

[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

Jan Kuester said...

Nice work! Just wondering, starting from line 152 in the kernel module:

Is 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 :)

Anurag Chugh said...

Hey Jan,

Its 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

Post a Comment