01
22

NETLINK-與kernel溝通的另一種方法

新年快到了,先祝大家新年快樂,牛年發大財,快到了年關,也該好好輕鬆一下,回頭看看自己這一年是不是做了些足以令你自傲的豐功偉業,也希望我能繼續保持對coding的熱誠在新的一年繼續奮鬥

話說回來,有了ioctl,和proc,我們是不是還有其它可以與kernel交換資料的界面呢?沒錯,就是netlink,那什麼時候會用到netlink呢?當你設routing table或防火牆時就會用到了,kernel就是用netlink界面與route指令溝通,scsi驅動也可以看到netlink的影子,netlink有什麼好處呢?

優點:
1.它沒有ioctl等ret回來的kernel round trip time
2.可發多個訊息給不同的user process(如果user processes都在同一group)
缺點:
1.因為只有UDP封包,所以你送資料後也不曉得該資料到底是不是去到對的地方
2.用了一堆socket API,程式碼寫起來並不簡潔

簡單描述我現在做的測試,以下面的kernel udp echo server為例,它會接收使用者的訊息並echo回來,所以整個程式分為兩部份,udp echo server在kernel space,使用者程式在user space

udp echo server重點列如下
1. netlink_kernel_create: create kernel socket並且註冊你的callback函式處理netlink封包,請特別注意第二個參數,我用了NETLINK_USERSOCK這個linux保留給使用者測試的netlink type.
2. sock_release:移除module時,一定要release kernel socket
3. udp_receive:這個函式就是我們處理netlink傳入封包的地方,在這裡你可以取得user process id和data
4. udp_reply:做一個新的封包並呼叫函式netlink_unicast echo回去

[kudp echo server]

  1. #include <linux/module.h> 
  2. #include <linux/kernel.h> 
  3. #include <linux/init.h> 
  4. #include <net/sock.h> 
  5. #include <net/netlink.h> 
  6. #include <linux/skbuff.h> 
  7.  
  8.  
  9. static struct sock *netlink_sock;
  10.  
  11. static void udp_reply(int pid,int seq,void *payload) 
  12. { 
  13. struct sk_buff *skb;
  14. struct nlmsghdr *nlh;
  15. int size=strlen(payload)+1;
  16. int len = NLMSG_SPACE(size);
  17. void *data;
  18. int ret;
  19.  
  20. skb = alloc_skb(len, GFP_ATOMIC);
  21. if (!skb) 
  22. return;
  23. nlh= NLMSG_PUT(skb, pid, seq, 0, size);
  24. nlh->nlmsg_flags = 0;
  25. data=NLMSG_DATA(nlh);
  26. memcpy(data, payload, size);
  27. NETLINK_CB(skb).pid = 0; /* from kernel */ 
  28. NETLINK_CB(skb).dst_group = 0; /* unicast */ 
  29. ret=netlink_unicast(netlink_sock, skb, pid, MSG_DONTWAIT);
  30. if (ret <0) 
  31. { 
  32. printk("send failed\n");
  33. return;
  34. } 
  35. return;
  36.  
  37. nlmsg_failure: /* Used by NLMSG_PUT */ 
  38. if (skb) 
  39. kfree_skb(skb);
  40. } 
  41.  
  42.  
  43. /* Receive messages from netlink socket. */ 
  44. static void udp_receive(struct sk_buff *skb) 
  45. { 
  46. u_int uid, pid, seq, sid;
  47. void *data;
  48. struct nlmsghdr *nlh;
  49.  
  50. nlh = (struct nlmsghdr *)skb->data;
  51. pid = NETLINK_CREDS(skb)->pid;
  52. uid = NETLINK_CREDS(skb)->uid;
  53. sid = NETLINK_CB(skb).sid;
  54. seq = nlh->nlmsg_seq;
  55. data = NLMSG_DATA(nlh);
  56. printk("recv skb from user space uid:%d pid:%d seq:%d,sid:%d\n",uid,pid,seq,sid);
  57. printk("data is :%s\n",(char *)data);
  58. udp_reply(pid,seq,data);
  59. return ;
  60. } 
  61.  
  62. static int __init kudp_init(void) 
  63. { 
  64. netlink_sock = netlink_kernel_create(&init_net, NETLINK_USERSOCK, 0,udp_receive, NULL, THIS_MODULE);
  65. return 0;
  66. } 
  67.  
  68. static void __exit kudp_exit(void) 
  69. { 
  70. sock_release(netlink_sock->sk_socket);
  71. printk("netlink driver remove successfully\n");
  72. } 
  73. module_init(kudp_init);
  74. module_exit(kudp_exit);
  75.  
  76. MODULE_DESCRIPTION("kudp echo server module");
  77. MODULE_AUTHOR("Joey Cheng<jemicheng@gmail.com>");
  78. MODULE_LICENSE("GPL");
  79. MODULE_ALIAS("QT2410:kudp echo server module");

user space client重點列如下
1.宣告socket fd請用sock_fd = socket(PF_NETLINK, SOCK_RAW,NETLINK_USERSOCK),第三個傳入參數為netlink type
2.注意pid與group的使用方式,如果只是unicast,要把group設為0
3.小心struct msghdr msg,要先把裡面的iov,nlmsghdr等欄位宣告並初始化
4.簡單的用sendmsg和recvmsg就大功告成啦
[user space client]

  1. #include <stdio.h> 
  2. #include <string.h> 
  3. #include <malloc.h> 
  4. #include <sys/socket.h> 
  5. #include <linux/netlink.h> 
  6.  
  7. #define MAX_PAYLOAD 1024
  8.  
  9. int main(int argc,char *argv[]) 
  10. { 
  11. struct sockaddr_nl src_addr, dest_addr;
  12. struct nlmsghdr *nlh = NULL;
  13. struct iovec iov;
  14. int sock_fd;
  15. struct msghdr msg;
  16.  
  17. sock_fd = socket(PF_NETLINK, SOCK_RAW,NETLINK_USERSOCK);
  18. memset(&src_addr, 0, sizeof(src_addr));
  19. src_addr.nl_family = AF_NETLINK;
  20. src_addr.nl_pid = getpid(); /* self pid */ 
  21. src_addr.nl_groups = 0; /* not in mcast groups */ 
  22. bind(sock_fd, (struct sockaddr*)&src_addr,sizeof(src_addr));
  23.  
  24. memset(&dest_addr, 0, sizeof(dest_addr));
  25. dest_addr.nl_family = AF_NETLINK;
  26. dest_addr.nl_pid = 0; /* For Linux Kernel */ 
  27. dest_addr.nl_groups = 0; /* unicast */ 
  28.  
  29. nlh=(struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
  30. /* Fill the netlink message header */ 
  31. nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
  32. nlh->nlmsg_pid = getpid(); /* self pid */ 
  33. nlh->nlmsg_flags = 0;
  34. /* Fill in the netlink message payload */ 
  35. strcpy(NLMSG_DATA(nlh), "Hello World!");
  36.  
  37. iov.iov_base = (void *)nlh;
  38. iov.iov_len = nlh->nlmsg_len;
  39. msg.msg_name = (void *)&dest_addr;
  40. msg.msg_namelen = sizeof(dest_addr);
  41. msg.msg_iov = &iov;
  42. msg.msg_iovlen = 1;
  43.  
  44. sendmsg(sock_fd, &msg, 0);
  45.  
  46. printf("Waiting for message from kernel\n");
  47.  
  48. /* Read message from kernel */ 
  49. recvmsg(sock_fd, &msg, 0);
  50.  
  51. printf("Received message payload: %s\n",NLMSG_DATA(msg.msg_iov->iov_base));
  52. close(sock_fd);
  53. return 0;
  54. }

全部的程式請在這邊下載,如果要嘗試multicast server,那麼請參考kdupb.c這支程式,和它的user space client mc1.c和mc2.c,只要group設定正確mc1和mc2都可以收到kudpb的multicast訊息,kudpb利用netlink_broadcast廣播給mc1和mc2,執行畫面如下

標籤: linux
評論: 2 | 引用: 0 | 閱讀: 30918
  • 1 
Cindy [ 2011-07-29 22:57 | 回覆 | 編輯 刪除 ]
您好!我想請問一些問題!!
請問您程式是在Linux kernel的版本為何的情況下測試的?
另外就是您給的unicast的code的執行順序為何?(是否先將kudp.ko insmod後再去執行user?)
如果是按照上面的順序~
我在Linux 2.6.38情況下測,先把kudp.ko insmod後再執行user(要用./user還是./user&)
但結果是./user後畫面就停在"Waiting for message from kernel",之後就不動了!
等很久結果還是一樣!!
想請問這是什麼情況呢?
謝謝!
intjohn [ 回復於2011-10-12 11:17 | 編輯 刪除 ]
Hi
試試在user.c裡給msg初始值
memset(&msg,0,sizeof(msg));
  • 1 
發表評論
暱 稱: 密 碼:
網 址: E - mail:
驗證碼: 驗證碼圖片 選 項:
頭 像:
內 容: