【轉】linux下netlink的使用簡介
阿新 • • 發佈:2018-11-10
原博文連線:http://blog.chinaunix.net/uid-24716553-id-5609723.html
netlink是一種用於使用者空間程序與核心間通訊的方法,也可以用於使用者程序之間的通訊(IPC)。netlink和ioctl比較相似,都能從使用者空間向核心空間通訊, 但netlink是一種非同步通訊機制,而ioctl是同步通訊機制。且ioctl不能從核心向用戶空間傳送訊息。
下面我們結合例項來進一步瞭解netlink。我們的例子是使用者態傳送一串資料給核心態,核心接收到後,返回"I am from kernel!"給使用者態程式。
核心態(以核心版本3.13為例)
使用netlink_kernel_create()函式建立核心套接字服務,函式型別:
- static inline struct sock *netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg);
函式引數net是網路名稱空間,如果沒自定義網路名稱空間的話,就使用核心預設的init_net。
unit是netlink型別,核心已經定義了21個(在include/uapi/linux/netlink.h中),如果要增加我們自己的netlink型別,就必須定義新的值,但要注意,新的值必須小於MAX_LINKS(32)
cfg是netlink的配置資訊。該結構體定義如下:
- struct netlink_kernel_cfg {
- unsigned int groups;
- unsigned int flags;
- void (*input)(struct sk_buff *skb);
- struct mutex *
- int (*bind)(struct net *net, int group);
- void (*unbind)(struct net *net, int group);
- bool (*compare)(struct net *net, struct sock *sk);
- };
來看程式碼:
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/timer.h>
- #include <linux/time.h>
- #include <linux/types.h>
- #include <net/sock.h>
- #include <net/netlink.h>
- #include <linux/string.h>
-
- #define NETLINK_TEST 25
- #define MAX_MSGSIZE 1024
-
- struct sock *nl_sk = NULL;
-
- void send_msg(char *message, int pid)
- {
- struct sk_buff *skb;
- struct nlmsghdr *nlh;
- int len = NLMSG_SPACE(MAX_MSGSIZE);
-
- if (!message || !nl_sk) {
- return;
- }
- printk(KERN_INFO "pid:%d\n", pid);
- skb = alloc_skb(len, GFP_KERNEL);
- if (!skb) {
- printk(KERN_ERR "send_msg:alloc_skb error\n");
- return;
- }
- nlh = nlmsg_put(skb, 0, 0, 0, MAX_MSGSIZE, 0);
- NETLINK_CB(skb).portid = 0;
- NETLINK_CB(skb).dst_group = 0;
- strcpy(NLMSG_DATA(nlh), message);
- printk(KERN_INFO "my_net_link:send message '%s'.\n",(char *)NLMSG_DATA(nlh));
- netlink_unicast(nl_sk, skb, pid, MSG_DONTWAIT);
- }
-
- void recv_nlmsg(struct sk_buff *skb)
- {
- int pid;
- struct nlmsghdr *nlh = nlmsg_hdr(skb);
-
- if (nlh->nlmsg_len < NLMSG_HDRLEN || skb->len < nlh->nlmsg_len)
- return;
-
- printk(KERN_INFO "Message received:%s\n", (char*)NLMSG_DATA(nlh));
- pid = nlh->nlmsg_pid;
- send_msg("I am from kernel!", pid);
- }
-
- struct netlink_kernel_cfg nl_kernel_cfg = {
- .groups = 0,
- .flags = 0,
- .input = recv_nlmsg,
- .cb_mutex = NULL,
- .bind = NULL,
- .compare = NULL,
- };
-
- int netlink_init(void)
- {
- nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, &nl_kernel_cfg);
- if (!nl_sk) {
- printk(KERN_ERR "my_net_link: create netlink socket error.\n");
- return 1;
- }
- printk(KERN_INFO "netlink_init: create netlink socket ok.\n");
- return 0;
- }
-
- static void netlink_exit(void)
- {
- if (nl_sk != NULL)
- sock_release(nl_sk->sk_socket);
-
- printk(KERN_INFO "my_net_link: self module exited\n");
- }
-
- module_init(netlink_init);
- module_exit(netlink_exit);
- MODULE_AUTHOR("vv1133");
- MODULE_LICENSE("GPL");
程式中,載入模組時,呼叫netlink_kernel_create()建立套接字。解除安裝模組時,呼叫sock_release()釋放套接字。
netlink資料包處理函式是recv_nlmsg(),首先通過nlmsg_hdr()函式獲取到netlink報頭,netlink的訊息報頭由include/uapi/linux/netlink.h中的nlmsghdr定義:
- struct nlmsghdr
- {
- __u32 nlmsg_len; /* Length of message including header */
- __u16 nlmsg_type; /* Message content */
- __u16 nlmsg_flags; /* Additional flags */
- __u32 nlmsg_seq; /* Sequence number */
- __u32 nlmsg_pid; /* Sending process PID */
- };
我們通過NLMSG_DATA獲取netlink的payload並打印出來。注意,netlink的payload一般使用TLV格式,即“型別-長度-值”,但這裡我們只傳遞一個固定的字串,簡單起見就不用這個格式了。然後,通過alloc_skb()函式建立一個新的skb,並用nlmsg_put()函式填入netlink結構,填充要傳送的字串。最後呼叫netlink_unicast()傳送資料。
使用者態
對於使用者態的程式,netlink通訊與一般的socket通訊類似。只是要用sendmsg()和recvmsg()代替send()/write()和recv()/read()具體步驟如下:
1. 建立套接字
使用者空間使用系統呼叫socket()建立netlink套接字,需要將域名設定成AF_NETLINK,型別必須是SOCK_RAW或SOCK_DGRAM,協議設定成我們的自定義netlink協議NETLINK_TEST。
2. 將本地套接字與源地址繫結
由於在netlink報頭裡要設定埠號,我們必須使用bind()函式將指定的埠與socket繫結。
3. 初始化msghdr
a. 設定目的地址資訊
- dest_addr.nl_family = AF_NETLINK;
- dest_addr.nl_pid = 0;
- dest_addr.nl_groups = 0;
- nlh = nlmsg_hdr(skb);
- nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
- nlh->nlmsg_pid = getpid();
- nlh->nlmsg_flags = 0;
- iov.iov_base = (void *)nlh;
- iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);
- struct msghdr msg;
- msg.msg_name = (void *)&dest_addr;
- msg.msg_namelen = sizeof(dest_addr);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
4. 呼叫sendmsg向核心傳送訊息,recvmsg從核心獲取訊息。
來看程式碼:
- #include <sys/stat.h>
- #include <unistd.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <sys/socket.h>
- #include <sys/types.h>
- #include <string.h>
- #include <asm/types.h>
- #include <linux/netlink.h>
- #include <linux/socket.h>
- #include <errno.h>
-
- #define MAX_PAYLOAD 1024
- #define NETLINK_TEST 25
-
- int main(int argc, char* argv[])
- {
- int state;
- struct sockaddr_nl src_addr, dest_addr;
- struct nlmsghdr *nlh = NULL;
- struct iovec iov;
- struct msghdr msg;
- int sock_fd, retval;
- int state_smg = 0;
-
- // Create a socket
- sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
- if(sock_fd == -1){
- printf("error getting socket: %s", strerror(errno));
- return -1;
- }
-
- // To prepare binding
- memset(&src_addr, 0, sizeof(src_addr));
- src_addr.nl_family = AF_NETLINK;
- src_addr.nl_pid = 100; //設定源端埠號
- src_addr.nl_groups = 0;
-
- //Bind
- retval = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));
- if (retval < 0) {
- printf("bind failed: %s", strerror(errno));
- close(sock_fd);
- return -1;
- }
-
- // To orepare create mssage
- nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
- if (!nlh) {
- printf("malloc nlmsghdr error!\n");
- close(sock_fd);
- return -1;
- }
-
- memset(&dest_addr,0,sizeof(dest_addr));
- dest_addr.nl_family = AF_NETLINK;
- dest_addr.nl_pid = 0; //設定目的埠號
- dest_addr.nl_groups = 0;
- nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
- nlh->nlmsg_pid = 100; //設定源埠
- nlh->nlmsg_flags = 0;
- strcpy(NLMSG_DATA(nlh), "Hello from client!"); //設定訊息體
- iov.iov_base = (void *)nlh;
- iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);
-
- //Create mssage
- memset(&msg, 0, sizeof(msg));
- msg.msg_name = (void *)&dest_addr;
- msg.msg_namelen = sizeof(dest_addr);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- //send message
- printf("state_smg\n");
- state_smg = sendmsg(sock_fd,&msg,0);
- if (state_smg == -1) {
- printf("get error sendmsg = %s\n",strerror(errno));
- }
- memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
-
- //receive message
- printf("waiting received!\n");
- state = recvmsg(sock_fd, &msg, 0);
- if (state < 0) {
- printf("state<1");
- }
- printf("Received message: %s\n", (char*)NLMSG_DATA(nlh));
-
- close(sock_fd);
- return 0;
- }
執行程式:
載入模組
- # sudo insmod netl.ko
執行客戶端程式
- # ./client
- state_smg
- waiting
- Received message: I am from
檢視核心列印
- # dmesg
- [ 545.278040] my_net_link: self module exited
- [ 679.799672] PPP MPPE Compression module registered
- [ 4368.567435] netlink_init: create netlink socket ok.
- [ 4379.239327] Message received:Hello from
- [ 4379.239331] pid:100
- [ 4379.239333] my_net_link:send message 'I am from kernel!'.
解除安裝模組
- # sudo rmmod netl