1. 程式人生 > >【轉】linux下netlink的使用簡介

【轉】linux下netlink的使用簡介

原博文連線:http://blog.chinaunix.net/uid-24716553-id-5609723.html

netlink是一種用於使用者空間程序與核心間通訊的方法,也可以用於使用者程序之間的通訊(IPC)。
netlink和ioctl比較相似,都能從使用者空間向核心空間通訊, 但netlink是一種非同步通訊機制,而ioctl是同步通訊機制。且ioctl不能從核心向用戶空間傳送訊息。

下面我們結合例項來進一步瞭解netlink。我們的例子是使用者態傳送一串資料給核心態,核心接收到後,返回"I am from kernel!"給使用者態程式。

核心態(以核心版本3.13為例)

先來了解下核心中netlink相關的資料結構和函式。

使用netlink_kernel_create()函式建立核心套接字服務,函式型別:
  1. static inline struct sock *netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg);
函式返回建立的struct sock物件指標。struct sock (定義在include\linux\net.h) 結構是核心網路系統中最常見的結構之一。它的使用基本貫穿L2,L3,L4層,而且是各層之間的一個聯絡,無論傳送還是接收的資料包都要被存到sock結構中的緩衝佇列中。
函式引數net是網路名稱空間,如果沒自定義網路名稱空間的話,就使用核心預設的init_net。
unit是netlink型別,核心已經定義了21個(在include/uapi/linux/netlink.h中),如果要增加我們自己的netlink型別,就必須定義新的值,但要注意,新的值必須小於MAX_LINKS(32)
cfg是netlink的配置資訊。該結構體定義如下:
  1. struct netlink_kernel_cfg {

  2.         unsigned int groups;
  3.         unsigned int flags;
  4.         void (*input)(struct sk_buff *skb);
  5.         struct mutex *
    cb_mutex;
  6.         int (*bind)(struct net *net, int group);
  7.         void (*unbind)(struct net *net, int group);
  8.         bool (*compare)(struct net *net, struct sock *sk);
  9. };
其中最重要的是input域,該函式用於處理使用者空間傳送到核心方向的資料。核心接收到資料包後,會傳給input函式。

來看程式碼:
  1. #include <linux/init.h>
  2. #include <linux/module.h>
  3. #include <linux/timer.h>
  4. #include <linux/time.h>
  5. #include <linux/types.h>
  6. #include <net/sock.h>
  7. #include <net/netlink.h>
  8. #include <linux/string.h>

  9. #define NETLINK_TEST 25
  10. #define MAX_MSGSIZE 1024

  11. struct sock *nl_sk = NULL;

  12. void send_msg(char *message, int pid)
  13. {
  14.         struct sk_buff *skb;
  15.         struct nlmsghdr *nlh;
  16.         int len = NLMSG_SPACE(MAX_MSGSIZE);

  17.         if (!message || !nl_sk) {
  18.                 return;
  19.         }
  20.         printk(KERN_INFO "pid:%d\n", pid);
  21.         skb = alloc_skb(len, GFP_KERNEL);
  22.         if (!skb) {
  23.                 printk(KERN_ERR "send_msg:alloc_skb error\n");
  24.                 return;
  25.         }
  26.         nlh = nlmsg_put(skb, 0, 0, 0, MAX_MSGSIZE, 0);
  27.         NETLINK_CB(skb).portid = 0;
  28.         NETLINK_CB(skb).dst_group = 0;
  29.         strcpy(NLMSG_DATA(nlh), message);
  30.         printk(KERN_INFO "my_net_link:send message '%s'.\n",(char *)NLMSG_DATA(nlh));
  31.         netlink_unicast(nl_sk, skb, pid, MSG_DONTWAIT);
  32. }

  33. void recv_nlmsg(struct sk_buff *skb)
  34. {
  35.         int pid;
  36.         struct nlmsghdr *nlh = nlmsg_hdr(skb);

  37.         if (nlh->nlmsg_len < NLMSG_HDRLEN || skb->len < nlh->nlmsg_len)
  38.                 return;

  39.         printk(KERN_INFO "Message received:%s\n", (char*)NLMSG_DATA(nlh));
  40.         pid = nlh->nlmsg_pid;
  41.         send_msg("I am from kernel!", pid);
  42. }

  43. struct netlink_kernel_cfg nl_kernel_cfg = {
  44.         .groups = 0,
  45.         .flags = 0,
  46.         .input = recv_nlmsg,
  47.         .cb_mutex = NULL,
  48.         .bind = NULL,
  49.         .compare = NULL,
  50. };

  51. int netlink_init(void)
  52. {
  53.         nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, &nl_kernel_cfg);
  54.         if (!nl_sk) {
  55.                 printk(KERN_ERR "my_net_link: create netlink socket error.\n");
  56.                 return 1;
  57.         }
  58.         printk(KERN_INFO "netlink_init: create netlink socket ok.\n");
  59.         return 0;
  60. }

  61. static void netlink_exit(void)
  62. {
  63.         if (nl_sk != NULL)
  64.                 sock_release(nl_sk->sk_socket);

  65.         printk(KERN_INFO "my_net_link: self module exited\n");
  66. }

  67. module_init(netlink_init);
  68. module_exit(netlink_exit);
  69. MODULE_AUTHOR("vv1133");
  70. MODULE_LICENSE("GPL");

程式中,載入模組時,呼叫netlink_kernel_create()建立套接字。解除安裝模組時,呼叫sock_release()釋放套接字。
netlink資料包處理函式是recv_nlmsg(),首先通過nlmsg_hdr()函式獲取到netlink報頭,netlink的訊息報頭由include/uapi/linux/netlink.h中的nlmsghdr定義:
  1. struct nlmsghdr
  2. {
  3.     __u32 nlmsg_len; /* Length of message including header */
  4.     __u16 nlmsg_type; /* Message content */
  5.     __u16 nlmsg_flags; /* Additional flags */
  6.     __u32 nlmsg_seq; /* Sequence number */
  7.     __u32 nlmsg_pid; /* Sending process PID */
  8. };

我們通過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. 設定目的地址資訊
  1. dest_addr.nl_family = AF_NETLINK;
  2. dest_addr.nl_pid = 0;
  3. dest_addr.nl_groups = 0;
b. 填充netlink報頭,即nlmsghdr結構體
  1. nlh = nlmsg_hdr(skb);
  2. nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
  3. nlh->nlmsg_pid = getpid();
  4. nlh->nlmsg_flags = 0;
c. 將緩衝區向量iovec與訊息進行繫結,指向訊息頭。
  1. iov.iov_base = (void *)nlh;
  2. iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);
d. 填充msghdr結構體
  1. struct msghdr msg;
  2. msg.msg_name = (void *)&dest_addr;
  3. msg.msg_namelen = sizeof(dest_addr);
  4. msg.msg_iov = &iov;
  5. msg.msg_iovlen = 1;

4. 呼叫sendmsg向核心傳送訊息,recvmsg從核心獲取訊息。

來看程式碼:
  1. #include <sys/stat.h>
  2. #include <unistd.h>
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <sys/socket.h>
  6. #include <sys/types.h>
  7. #include <string.h>
  8. #include <asm/types.h>
  9. #include <linux/netlink.h>
  10. #include <linux/socket.h>
  11. #include <errno.h>

  12. #define MAX_PAYLOAD 1024
  13. #define NETLINK_TEST 25

  14. int main(int argc, char* argv[])
  15. {
  16.         int state;
  17.         struct sockaddr_nl src_addr, dest_addr;
  18.         struct nlmsghdr *nlh = NULL;
  19.         struct iovec iov;
  20.         struct msghdr msg;
  21.         int sock_fd, retval;
  22.         int state_smg = 0;

  23.         // Create a socket
  24.         sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
  25.         if(sock_fd == -1){
  26.                 printf("error getting socket: %s", strerror(errno));
  27.                 return -1;
  28.         }

  29.         // To prepare binding
  30.         memset(&src_addr, 0, sizeof(src_addr));
  31.         src_addr.nl_family = AF_NETLINK;
  32.         src_addr.nl_pid = 100; //設定源端埠號
  33.         src_addr.nl_groups = 0;

  34.         //Bind
  35.         retval = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));
  36.         if (retval < 0) {
  37.                 printf("bind failed: %s", strerror(errno));
  38.                 close(sock_fd);
  39.                 return -1;
  40.         }

  41.         // To orepare create mssage
  42.         nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
  43.         if (!nlh) {
  44.                 printf("malloc nlmsghdr error!\n");
  45.                 close(sock_fd);
  46.                 return -1;
  47.         }

  48.         memset(&dest_addr,0,sizeof(dest_addr));
  49.         dest_addr.nl_family = AF_NETLINK;
  50.         dest_addr.nl_pid = 0; //設定目的埠號
  51.         dest_addr.nl_groups = 0;
  52.         nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
  53.         nlh->nlmsg_pid = 100; //設定源埠
  54.         nlh->nlmsg_flags = 0;
  55.         strcpy(NLMSG_DATA(nlh), "Hello from client!"); //設定訊息體
  56.         iov.iov_base = (void *)nlh;
  57.         iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);

  58.         //Create mssage
  59.         memset(&msg, 0, sizeof(msg));
  60.         msg.msg_name = (void *)&dest_addr;
  61.         msg.msg_namelen = sizeof(dest_addr);
  62.         msg.msg_iov = &iov;
  63.         msg.msg_iovlen = 1;

  64.         //send message
  65.         printf("state_smg\n");
  66.         state_smg = sendmsg(sock_fd,&msg,0);
  67.         if (state_smg == -1) {
  68.                 printf("get error sendmsg = %s\n",strerror(errno));
  69.         }
  70.         memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));

  71.         //receive message
  72.         printf("waiting received!\n");
  73.         state = recvmsg(sock_fd, &msg, 0);
  74.         if (state < 0) {
  75.                 printf("state<1");
  76.         }
  77.         printf("Received message: %s\n", (char*)NLMSG_DATA(nlh));

  78.         close(sock_fd);
  79.         return 0;
  80. }

執行程式:

載入模組
  1. # sudo insmod netl.ko

執行客戶端程式
  1. # ./client
  2. state_smg
  3. waiting
  4. Received message: I am from

檢視核心列印
  1. # dmesg
  2. [ 545.278040] my_net_link: self module exited
  3. [ 679.799672] PPP MPPE Compression module registered
  4. [ 4368.567435] netlink_init: create netlink socket ok.
  5. [ 4379.239327] Message received:Hello from
  6. [ 4379.239331] pid:100
  7. [ 4379.239333] my_net_link:send message 'I am from kernel!'.

解除安裝模組
  1. # sudo rmmod netl