Netlinkプログラミングの書き方
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.ordermakeコマンドを実行して、カーネルモジュールを作成します。
[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技術ブログ

