Linux UK Communication: Netlink
上一篇文章所介紹使用的 Linux User/Kernel 溝通方式:Character Device,是一個很標準的範例程式,過去在公司的案子開發上也大多採用這種方法(另一種方法是 ioctl )。這個方式最大的好處是簡單,而且移植性高,因為從很早以前的 Linux Kernel 就已經支援了(2.4,你問 2.2?抱歉,我沒那麼老 ... )缺點呢?Kernel Module 只能夠被動的等待 User Space 的程式呼叫,無法主動進行溝通。
Linux Kernel 最近發展的方向,在 UK 這一塊盡可能改採 Netlink 的方式來進行通訊。這是一個很簡單的思維:既然所有網路的封包都會到 Kernel 去進行處理,那我為什麼不直接把網路封包拿來當作給 Kernel 的訊息呢?而 Kernel 要和 User Space 的程式溝通時,只要製作一個網路封包往 socket 丟不就好了嗎?於是 Netlink 就誕生了。先說缺點,因為算是比較新的技術,所以 API 的介面改的很快,因此程式似乎也要常常變動。下面是由同一位同事 taco 所撰寫的範例。(其實我本來想自己寫的,但 ... 懶啊)
Kernel:
User:
從程式碼可以看出來,User Space的程式和一般的 Socket 程式沒啥兩樣,除了要包一些特殊的 Header 而已。而 Kernel Space 的程式就是 skb 的處理。
Linux Kernel 最近發展的方向,在 UK 這一塊盡可能改採 Netlink 的方式來進行通訊。這是一個很簡單的思維:既然所有網路的封包都會到 Kernel 去進行處理,那我為什麼不直接把網路封包拿來當作給 Kernel 的訊息呢?而 Kernel 要和 User Space 的程式溝通時,只要製作一個網路封包往 socket 丟不就好了嗎?於是 Netlink 就誕生了。先說缺點,因為算是比較新的技術,所以 API 的介面改的很快,因此程式似乎也要常常變動。下面是由同一位同事 taco 所撰寫的範例。(其實我本來想自己寫的,但 ... 懶啊)
Kernel:
#include <linux/types.h> #include <linux/kernel.h> #include <linux/netlink.h> #include <net/sock.h> #include <linux/sched.h> #include <linux/module.h> #include <linux/init.h> MODULE_AUTHOR("tacolin"); MODULE_DESCRIPTION("NETLINK KERNEL TEST MODULE"); MODULE_LICENSE("GPL"); #define NETLINK_TEST 17 #define MAX_PAYLOAD 512 static struct sock* g_pSocket = NULL; static void recv_msg_from_user(struct sk_buff *pSkb, char* pRecvBuf, int *pPid) { struct nlmsghdr *pNlhdr = (struct nlmsghdr*)(pSkb->data); memcpy(pRecvBuf, nlmsg_data(pNlhdr), strlen( (char*)nlmsg_data(pNlhdr) ) ); *pPid = pNlhdr->nlmsg_pid; } static void send_msg_to_user(struct sock* pSocket, int pid, char* pSendBuf) { int msgSize = 0; int sendResult = -1; struct sk_buff *pSkb; struct nlmsghdr *pNlhdr; msgSize = strlen(pSendBuf); pSkb = nlmsg_new( msgSize, 0 ); pNlhdr = nlmsg_put(pSkb, 0, 0, NLMSG_DONE, msgSize, 0); sprintf( nlmsg_data(pNlhdr), pSendBuf, msgSize ); NETLINK_CB(pSkb).dst_group = 0; sendResult = nlmsg_unicast(pSocket, pSkb, pid); if ( sendResult < 0 ) { printk("[KERNEL-PART] unicast a message to user failed\n"); } } static void process_user_msg(struct sk_buff *pSkb) { int pid = -1; char pMsgBuf[MAX_PAYLOAD]; memset(pMsgBuf, 0, MAX_PAYLOAD); recv_msg_from_user(pSkb, pMsgBuf, &pid); printk("[KERNEL-PART] received pid=%d's message : %s\n", pid, pMsgBuf); memset(pMsgBuf, 0, MAX_PAYLOAD); sprintf(pMsgBuf, "Hello from Kernel"); send_msg_to_user(g_pSocket, pid, pMsgBuf); } static int __init kernel_module_init(void) { g_pSocket = netlink_kernel_create(&init_net, NETLINK_TEST, 0, process_user_msg, NULL, THIS_MODULE); printk("[KERNEL-PART] Module Inserted\n"); return 0; } static void __exit kernel_module_exit(void) { netlink_kernel_release(g_pSocket); printk("[KERNEL-PART] Module removed\n"); return; } module_init( kernel_module_init ); module_exit( kernel_module_exit );
User:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <linux/netlink.h> #include <linux/socket.h> #define NETLINK_TEST 17 #define MAX_PAYLOAD 512 int main(int argc, char* argv[]) { int socketFd; socketFd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST); struct sockaddr_nl srcAddr; memset( &srcAddr, 0, sizeof(srcAddr) ); srcAddr.nl_family = AF_NETLINK; srcAddr.nl_pid = getpid(); srcAddr.nl_groups = 0; bind( socketFd, (struct sockaddr*)&srcAddr, sizeof(srcAddr) ); struct sockaddr_nl dstAddr; memset( &dstAddr, 0, sizeof(dstAddr) ); dstAddr.nl_family = AF_NETLINK; dstAddr.nl_pid = 0; dstAddr.nl_groups = 0; struct nlmsghdr *pNlhdr; pNlhdr = (struct nlmsghdr*)malloc( NLMSG_SPACE(MAX_PAYLOAD) ); memset( pNlhdr, 0, NLMSG_SPACE(MAX_PAYLOAD) ); pNlhdr->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD); pNlhdr->nlmsg_pid = getpid(); pNlhdr->nlmsg_flags = 0; sprintf( NLMSG_DATA(pNlhdr), "Hello from User" ); struct iovec iov; memset( &iov, 0, sizeof(struct iovec) ); iov.iov_base = (void*)pNlhdr; iov.iov_len = pNlhdr->nlmsg_len; struct msghdr msg; memset( &msg, 0, sizeof(struct msghdr) ); msg.msg_name = (void*)&dstAddr; msg.msg_namelen = sizeof(dstAddr); msg.msg_iov = &iov; msg.msg_iovlen = 1; sendmsg( socketFd, &msg, 0 ); memset( pNlhdr, 0, NLMSG_SPACE(MAX_PAYLOAD) ); recvmsg( socketFd, &msg, 0 ); printf("[USER-PART] Receive message from kernel : %s\n", (char*)NLMSG_DATA(pNlhdr) ); close(socketFd); return 0; }
從程式碼可以看出來,User Space的程式和一般的 Socket 程式沒啥兩樣,除了要包一些特殊的 Header 而已。而 Kernel Space 的程式就是 skb 的處理。
留言
張貼留言