1 Netlinkとは?
Netlinkとは、ユーザー空間とカーネル空間で情報をやりとりするためのインタフェースです。以下は、ユーザプロセスとカーネルモジュールがNetlinkインタフェースを使ってやり取りをしているところを図示したものです。
ユーザ空間 +-----------------------+ | | | ユーザプロセス | | | +-----------------------+ ↑ | --------------------------|------------ Netlinkインタフェース カーネル空間 | ↓ +-----------------------+ | | | カーネルモジュール | | | +-----------------------+
Netlinkを使ってメッセージを送信するには、socketシステムコールの第1パラメータにAF_NETLINKを指定します。Netlinkを使っているユーザプロセスには、ipコマンドやdbusデーモン等があります。たとえば、ipコマンドを実行すると、socketシステムコールの第1パラメータにAF_NETLINKを指定していることがわかります。
[root@server ~]# strace -e trace=socket ip l socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_ROUTE) = 3
なお、straceコマンドの使い方は、straceコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。
2. 検証環境
CentOS版数は次のとおりです。
[root@server ~]# cat /etc/redhat-release CentOS Linux release 7.9.2009 (Core)
カーネル版数は次のとおりです。
[root@server ~]# uname -r 3.10.0-1160.el7.x86_64
3 サンプルプログラムの作成
3.1 カーネルモジュールの作成
カーネルモジュールを作成します。カーネルモジーュルは、カーネル空間で動作するプロセスです。ユーザ空間のプロセスが送信するメッセージを受信するカーネルモジュールを作成します。netlink_kernel_createの第2パラメータは、include/uapi/linux/netlink.hで未定義な値(17)を使いました。
[root@server ~]# cat netlink_module.c #include <linux/module.h> #include <linux/netlink.h> #include <linux/init.h> #include <net/sock.h> #define NETLINK_TEST 17 static struct sock *nl_sock; char *msg="kernel message"; static void nl_recv_msg (struct sk_buff *skb_in) { struct nlmsghdr *nlh; struct sk_buff *skb_out; int msg_size,pid; // receiving from user process nlh = (struct nlmsghdr *)skb_in->data; pid = nlh->nlmsg_pid; printk(KERN_INFO "received netlink message(%s) from PID(%d)\n", (char*)nlmsg_data(nlh), pid); // sending to user process msg_size=strlen(msg); skb_out = nlmsg_new(msg_size, 0); nlh=nlmsg_put(skb_out, 0, 0, NLMSG_DONE, msg_size, 0); NETLINK_CB(skb_out).dst_group = 0; /* not in mcast group */ strncpy(nlmsg_data(nlh), msg, msg_size); nlmsg_unicast(nl_sock, skb_out, pid); } struct netlink_kernel_cfg cfg = { .input = nl_recv_msg, }; static int __init netlink_module_init(void) { printk(KERN_INFO "NETLINK TEST START\n"); nl_sock = netlink_kernel_create(&init_net, NETLINK_TEST, &cfg); return 0; } static void __exit netlink_module_exit(void) { printk(KERN_INFO "NETLINK TEST FINISH\n"); netlink_kernel_release(nl_sock); } module_init(netlink_module_init); module_exit(netlink_module_exit); MODULE_AUTHOR("hana_shin"); MODULE_LICENSE("GPL v2");
次に、Makefileを作成します。$(MAKE)やrmの前はスペースではなくTABです。
[root@server ~]# cat Makefile obj-m := netlink_module.o KDIR := /lib/modules/$(shell uname -r)/build VERBOSE = 0 all: $(MAKE) -C $(KDIR) M=$(PWD) KBUILD_VERBOSE=$(VERBOSE) modules clean: rm -f *.o *.ko *.mod.c Module.symvers modules.order
makeコマンドを実行して、カーネルモジュールを作成します。
[root@server ~]# make make -C /lib/modules/3.10.0-1160.el7.x86_64/build M=/root KBUILD_VERBOSE=0 modules make[1]: ディレクトリ `/usr/src/kernels/3.10.0-1160.el7.x86_64' に入ります CC [M] /root/netlink_module.o Building modules, stage 2. MODPOST 1 modules CC /root/netlink_module.mod.o LD [M] /root/netlink_module.ko make[1]: ディレクトリ `/usr/src/kernels/3.10.0-1160.el7.x86_64' から出ます
作成したカーネルモジュールを確認します。netlink_module.koが作成されていることがわかります。
[root@server ~]# ls -l netlink_module.* -rw-r--r--. 1 root root 1312 1月 10 22:27 netlink_module.c -rw-r--r--. 1 root root 413968 1月 10 22:30 netlink_module.ko -rw-r--r--. 1 root root 1337 1月 10 22:30 netlink_module.mod.c -rw-r--r--. 1 root root 59384 1月 10 22:30 netlink_module.mod.o -rw-r--r--. 1 root root 358480 1月 10 22:30 netlink_module.o
3.2 ユーザプログラムの作成
ユーザ空間で動作するプログラムを作成します。
[root@server ~]# cat netlink_user.c #include <stdlib.h> #include <string.h> #include <stdio.h> #include <unistd.h> #include <sys/socket.h> #include <linux/netlink.h> #define NETLINK_USER 17 #define MAX_PAYLOAD 1024 struct sockaddr_nl src_addr, dest_addr; struct nlmsghdr *nlh = NULL; struct iovec iov; int sock_fd; struct msghdr msg; int main(int argc, char *argv[]) { sock_fd=socket(PF_NETLINK, SOCK_RAW, NETLINK_USER); memset(&src_addr, 0, sizeof(src_addr)); src_addr.nl_family = AF_NETLINK; src_addr.nl_pid = getpid(); bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr)); memset(&dest_addr, 0, sizeof(dest_addr)); dest_addr.nl_family = AF_NETLINK; dest_addr.nl_pid = 0; dest_addr.nl_groups = 0; nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD)); memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD)); nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD); nlh->nlmsg_pid = getpid(); nlh->nlmsg_flags = 0; strcpy(NLMSG_DATA(nlh), argv[1]); iov.iov_base = (void *)nlh; iov.iov_len = nlh->nlmsg_len; msg.msg_name = (void *)&dest_addr; msg.msg_namelen = sizeof(dest_addr); msg.msg_iov = &iov; msg.msg_iovlen = 1; printf("My PID is %d\n", getpid()); printf("Sending message to kernel: %s\n", argv[1]); /* Send message to kernel */ sendmsg(sock_fd,&msg,0); printf("Waiting for message from kernel\n"); /* Read message from kernel */ recvmsg(sock_fd, &msg, 0); printf("Received message from kernel: %s\n", (char *)NLMSG_DATA(nlh)); close(sock_fd); return 0; }
コンパイルします。
[root@server ~]# gcc -Wall -o netlink_user netlink_user.c
実行ファイルを確認します。
[root@server ~]# ls netlink_user* netlink_user netlink_user.c
4 動作確認
サンプルプログラムが出力するログを確認するため、journalctlコマンドを実行します。
[root@server ~]# journalctl -f
カーネルモジュールをロードします。
[root@server ~]# insmod netlink_module.ko
ロードしたカーネルモジュールを確認します。作成したカーネルモジュールがロードされていることがわかります。
[root@server ~]# lsmod |grep netlink_module netlink_module 12558 0
任意の文字列(11111)を指定してサンプルプログラム(netlink_user)を実行します。ユーザプロセスのPIDは4524であることがわかります。また、カーネルに"11111"を送信し、カーネルから"kernel message"というメッセージを受信していることがわかります。
[root@server ~]# ./netlink_user 11111 My PID is 4524 Sending message to kernel: 11111 Waiting for message from kernel Received message from kernel: kernel message
カーネルモジュールが、PID=4524のユーザプロセスからメッセージを受信していることがわかります。
[root@server ~]# journalctl -f -snip- 1月 10 22:38:19 server kernel: NETLINK TEST START 1月 10 22:39:47 server kernel: received netlink message(11111) from PID(4524)
後始末をします。netlink_moduleをアンロードします。
[root@server ~]# rmmod netlink_module
モジュールを確認します。netlink_moduleがアンロードされたことがわかります。
[root@server ~]# lsmod |grep netlink_module [root@server ~]#
5 Netlinkメッセージ形式
ユーザプロセスからカーネルモジュールに送信される Netlinkメッセージを以下に示します。
dest_addr sockaddr_nl +----> +---------------+ | | nl_family | <= AF_NETLINKを設定する。 | +---------------+ | | nl_pad | | +---------------+ msghdr | | nl_pid | <= プロセスのPID(getpid)を設定する。 msg -> +----------------+ | +---------------+ | *msg_name | -----+ | nl_groups | <= ユニキャスト通信なので0を設定する。 +----------------+ +---------------+ | msg_namelen(*1)| +----------------+ iovec | *msg_iov | ----------> +---------------+ nlh |<- nlmsghdr ->| +----------------+ | *iov_base | --------> +--------------+-----------------------+ | msg_iovlen(*2) | +---------------+ | | | +----------------+ | iov_len(1024) | ----+ | 16(byte) | | <= malloc()で獲得する。 | msg_controllen | +---------------+ | | | | +----------------+ | +--------------+-----------------------+ | msg_flags | | |<------------ 1024(byte) ------------>| +----------------+ | A | | +----------------------+
(*1) sockaddr_nl構造体のサイズを設定する。
(*2) iovec構造体の個数(1個)を設定する。
Z 参考情報
私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ