天天看點

netlink 通信機制

下面的代碼部分取自于:http://blog.chinaunix.net/uid-14753126-id-2983915.html

不過在該基礎上修改了一下。(部分我認為是不合理的)

希望有所幫助咯~~~~~

一、自定義協定 netlik程式設計,有幾個地方需要注意的。 1.就是資料包的格式. struct nlmsghdr + padding + payload + padding 1,1 資料包占用的記憶體大小空間(包括paddnig)由宏NLMSG_SPACE可以擷取到,它就是用來調用sendto函數時候傳遞過去的長度參數, 指明了資料包占用的記憶體大小空間。 1.2 除了sendto函數參數需要指明資料占據空間大小之後,struct nlmsghdr的字段__u32 nlmsg_len; 我認為,該字段是用來儲存對齊後的頭,加上payload長度(沒有對齊)。通過宏NLMSG_LENGTH的傳回值作為該字段的值。之是以這樣認為,因為在核心 才可以得到payload的有效長度,通過struct nlmsghdr->nlmsg_len - NLMSG_ALIGN(NLMSG_HDRLEN)就可以得到payload的有效長度。 這個猜想,是可以通過核心函數__nlmsg_put來驗證的,該函數如下: static __inline__ struct nlmsghdr * __nlmsg_put(struct sk_buff *skb, u32 pid, u32 seq, int type, int len, int flags) { struct nlmsghdr *nlh; int size = NLMSG_LENGTH(len);   nlh = (struct nlmsghdr*)skb_put(skb, NLMSG_ALIGN(size)); nlh->nlmsg_type = type; nlh->nlmsg_len = size; nlh->nlmsg_flags = flags; nlh->nlmsg_pid = pid; nlh->nlmsg_seq = seq; if (!__builtin_constant_p(size) || NLMSG_ALIGN(size) - size != 0) memset(NLMSG_DATA(nlh) + len, 0, NLMSG_ALIGN(size) - size); return nlh; } 顯然,它設定的nlmsg_len就是有效大小!!!   實際當中,假設假設對核心的某個狀态感興趣,就可以把該消息發送出來給應用層。就下面的示例來說,可以在稍加修改。 核心一個靜态變量儲存了應用層的PID号,一開始可以初始化為0,當收到應用層的消息的時候,設定接收消息的PID号,這樣 核心就可以發送消息給應用層了。(因為知道了接受消息的PID号)。 對核心來說,隻有在PID号不為0的情況下,才需要去發送消息。(不為0的情況下,證明說應用程式已經啟動了)   netlink使用者程式: #include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <string.h> #include <linux/netlink.h>   #define NETLINK_TEST 17 #define MSG_LEN 100   struct msg_to_kernel { struct nlmsghdr hdr; char data[MSG_LEN]; }; struct u_packet_info { struct nlmsghdr hdr; char msg[MSG_LEN]; };   int main(int argc, char* argv[]) { char *data = "abcd"; int real_size = NLMSG_SPACE(strlen(data) + 1); //這個宏非常的重要!!需要差別他們之間的不同..(我這裡是簡單的命名為 int effective_size = NLMSG_LENGTH(strlen(data) + 1); //有效大小和實際大小)有效大小為沒有對齊後的大小,也就是真正payload資料長度。 struct sockaddr_nl local; //而且有這樣的關系 readl_size >= effective_size. 當有效大小為4的倍數的時候 struct sockaddr_nl kpeer; //就不需要對齊了,此時read_size = effective_size. int skfd, ret, kpeerlen = sizeof(struct sockaddr_nl); struct nlmsghdr *message; struct u_packet_info info; char *retval;   message = (struct nlmsghdr *)malloc(real_size); //建立socket的時候指定family為NETLINK,并且要指定協定。這裡是使用者自定義的一個協定。 skfd = socket(PF_NETLINK, SOCK_RAW, NETLINK_TEST); if(skfd < 0){ printf("can not create a netlink socket\n"); return -1; } memset(&local, 0, sizeof(local)); local.nl_family = AF_NETLINK; local.nl_pid = getpid(); local.nl_groups = 0; //調用bind指令,把目前的socket綁定一個pid号(類似于端口号),同時綁定可以确定該socket是否監聽組的消息。 //相同協定(eg:NETLINK_TEST)的socket,都應該有唯一的pid号. if(bind(skfd, (struct sockaddr *)&local, sizeof(local)) != 0){ printf("bind() error\n"); return -1; } //設定接收該消息的socket....(pid号指定一個socket..對于核心來說,它就是0)。同時也指定該消息要不要發送給哪個組。 memset(&kpeer, 0, sizeof(kpeer)); kpeer.nl_family = AF_NETLINK; kpeer.nl_pid = 0; kpeer.nl_groups = 0;   memset(message, '\0', real_size); message->nlmsg_len = effective_size; message->nlmsg_flags = 0; message->nlmsg_type = 0; message->nlmsg_seq = 0; message->nlmsg_pid = local.nl_pid;   retval = memcpy(NLMSG_DATA(message), data, effective_size);   printf("message sendto kernel are:%s, len:%d\n", (char *)NLMSG_DATA(message), message->nlmsg_len - NLMSG_ALIGN(NLMSG_HDRLEN)); ret = sendto(skfd, message, real_size, 0,(struct sockaddr *)&kpeer, sizeof(kpeer)); if(!ret){ perror("send pid:"); exit(-1); }   //接受核心态确認資訊 ret = recvfrom(skfd, &info, sizeof(struct u_packet_info),0, (struct sockaddr*)&kpeer, &kpeerlen); if(!ret){ perror("recv form kerner:"); exit(-1); }   printf("message receive from kernel:%s\n",(char *)info.msg);   close(skfd); return 0; }   netlink核心程式: #include <linux/init.h> #include <linux/module.h> #include <linux/types.h> #include <net/sock.h> #include <linux/netlink.h>   #define NETLINK_TEST 17 struct { __u32 pid; }user_process;   static struct sock *netlinkfd = NULL;   int send_to_user(char *info) { int effective_size = strlen(info) + 1; struct sk_buff *skb; struct nlmsghdr *nlh;   int retval; skb = nlmsg_new(effective_size, GFP_KERNEL); //這裡面會去配置設定skb...并且配置設定存放資料的記憶體塊。 if(!skb) goto err_out;   nlh = NLMSG_NEW(skb, 0, 0, 0, effective_size, 0); //這裡面就是初始化struct nlmsg_hdr頭. if(!nlh) goto err_out;   memcpy(NLMSG_DATA(nlh), info, effective_size); //拷貝資料到data區 NETLINK_CB(skb).pid = 0; //設定發送該消息的pid号. NETLINK_CB(skb).dst_group = 0; //設定接收該消息的組号.   retval = netlink_unicast(netlinkfd, skb, user_process.pid, MSG_DONTWAIT);   return 0;   nlmsg_failure: err_out: if(skb) kfree_skb(skb); return -1; }   void kernel_receive(struct sk_buff *__skb) { struct sk_buff *skb; struct nlmsghdr *nlh = NULL;   char *data = "This is eric's test message from kernel";   printk("[kernel space] begin kernel_receive\n"); skb = skb_get(__skb);   if(skb->len >= sizeof(struct nlmsghdr)){ nlh = (struct nlmsghdr *)skb->data; if((nlh->nlmsg_len >= sizeof(struct nlmsghdr)) && (__skb->len >= nlh->nlmsg_len)) {   user_process.pid = nlh->nlmsg_pid; printk("[kernel space] data receive from user are:%s\n", (char *)NLMSG_DATA(nlh)); printk("[kernel space] user_pid:%d\n", user_process.pid); send_to_user(data);   } }else{ printk("[kernel space] data receive from user are:%s\n",(char *)NLMSG_DATA(nlmsg_hdr(__skb))); send_to_user(data); }   kfree_skb(skb); }   int __init test_netlink_init(void) { netlinkfd = netlink_kernel_create(&init_net, NETLINK_TEST, 0, kernel_receive, NULL, THIS_MODULE); if(!netlinkfd){ printk("can not create a netlink socket\n"); return -1; } printk("test_netlink_init!!\n"); return 0; }   void __exit test_netlink_exit(void) { sock_release(netlinkfd->sk_socket); printk("test_netlink_exit!!\n"); }   module_init(test_netlink_init); module_exit(test_netlink_exit);   MODULE_AUTHOR("eric.hu"); MODULE_LICENSE("GPL");   二、接受來自核心的多點傳播消息。 netlink使用者程式,多點傳播。 下面這個程式是擷取核心多點傳播的消息,當核心檢測到說有新的路由添加的時候,它就會多點傳播一條消息,對于需要檢測該消息的程式就可以收到咯。 核心發送過來的消息形式為: struct nlmsghdr + padding + struct rtmsg + padding + struct nlattr + padding + struct nlattr + padding.... 一共會收到8個消息,可以檢視核心函數fib_add_ifaddr驗證結果。     #include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <string.h> #include <linux/netlink.h> #include <linux/rtnetlink.h>   int main() { struct sockaddr_nl local; struct sockaddr_nl kpeer; int skfd, rcv_size, kpeerlen = sizeof(struct sockaddr_nl); char data[4096]; struct nlmsghdr *hdr; struct rtmsg *rt; //struct nlattr + padding + payload + padding. struct nlattr *attr; unsigned int attr_size = NLA_ALIGN(NLA_HDRLEN + 4); unsigned int attr_num, i; unsigned char *p;   skfd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if(skfd < 0){ printf("can not create a netlink socket\n"); return -1; }   memset(&local, 0, sizeof(local)); local.nl_family = AF_NETLINK; local.nl_pid = getpid(); 要監聽的組的對應bit需要設定為1,因為bit是從0開始編号的,是以需要減1。 local.nl_groups = 1<<(RTNLGRP_IPV4_ROUTE - 1); if(bind(skfd, (struct sockaddr *)&local, sizeof(local)) != 0){ printf("bind() error\n"); return -1; }   while(1) { memset(&kpeer, 0, sizeof(kpeer)); rcv_size = recvfrom(skfd, data, 4096,0, (struct sockaddr*)&kpeer, &kpeerlen); if(rcv_size <= 0) { printf("Error, function: recvfrom.\n"); exit(-1); }   hdr = (struct nlmsghdr *)data; rt = (struct rtmsg *)(data + NLMSG_ALIGN(NLMSG_HDRLEN)); attr = (struct nlattr *)(data + NLMSG_ALIGN(NLMSG_HDRLEN) + NLMSG_ALIGN(sizeof(struct rtmsg))); printf("type: %d\t", hdr->nlmsg_type); printf("rtm_dst_len:%d\n", rt->rtm_dst_len); #if 0 p = attr; for(i = 0; i < 32; i++) { printf("%02x ", p[i]); } printf("\n"); #endif attr_num = RTM_PAYLOAD(hdr) / attr_size; for(i = 0; i < attr_num; i++) { printf("\tnla_len:%d\t", attr->nla_len); printf("\tnla_type:%d\t", attr->nla_type); if(attr->nla_type == 1) { p = (unsigned char *)attr + NLA_HDRLEN; printf("%d.%d.%d.%d", p[0], p[1], p[2], p[3]); } printf("\n"); //next attr. attr = (struct nlattr *)((unsigned char *)attr + attr_size); }   #if 0 printf("len: %d\n", hdr->nlmsg_len); printf("type: %d\n", hdr->nlmsg_type); printf("pid: %d\n", hdr->nlmsg_pid);   printf("rtm_family:%d\n", rt->rtm_family); printf("rtm_dst_len:%d\n", rt->rtm_dst_len); printf("nla_len:%d\n", attr->nla_len); printf("nla_type:%d\n", attr->nla_type); #endif } return 0; } 執行:            

netlink 通信機制

  顯示:  

netlink 通信機制

下面的宏,會作為struct nlmsghdr-> nlmsg_type的值. enum { ... ... RTM_NEWROUTE = 24, 添加路由 #define RTM_NEWROUTE RTM_NEWROUTE RTM_DELROUTE, 删出路由 #define RTM_DELROUTE RTM_DELROUTE RTM_GETROUTE, #define RTM_GETROUTE RTM_GETROUTE ... ... __RTM_MAX, #define RTM_MAX (((__RTM_MAX + 3) & ~3) - 1) }; 核心函數: void fib_add_ifaddr(struct in_ifaddr *ifa) { struct in_device *in_dev = ifa->ifa_dev; struct net_device *dev = in_dev->dev; struct in_ifaddr *prim = ifa; __be32 mask = ifa->ifa_mask; __be32 addr = ifa->ifa_local; __be32 prefix = ifa->ifa_address&mask;   if (ifa->ifa_flags&IFA_F_SECONDARY) { prim = inet_ifa_byprefix(in_dev, prefix, mask); if (prim == NULL) { printk(KERN_WARNING "fib_add_ifaddr: bug: prim == NULL\n"); return; } } //列印出來的type = 24, 192.168.1.173 fib_magic(RTM_NEWROUTE, RTN_LOCAL, addr, 32, prim);   if (!(dev->flags&IFF_UP)) return;   //列印出來的type = 25, 192.168.1.255 if (ifa->ifa_broadcast && ifa->ifa_broadcast != htonl(0xFFFFFFFF)) fib_magic(RTM_NEWROUTE, RTN_BROADCAST, ifa->ifa_broadcast, 32, prim);   if (!ipv4_is_zeronet(prefix) && !(ifa->ifa_flags&IFA_F_SECONDARY) && (prefix != addr || ifa->ifa_prefixlen < 32)) { //列印出來的type = 24, 192.168.1.0 fib_magic(RTM_NEWROUTE, dev->flags&IFF_LOOPBACK ? RTN_LOCAL : RTN_UNICAST, prefix, ifa->ifa_prefixlen, prim);   if (ifa->ifa_prefixlen < 31) { //列印的type = 24, 192.168.1.0 fib_magic(RTM_NEWROUTE, RTN_BROADCAST, prefix, 32, prim); //我猜測!!!沒有驗證...這裡也是添加廣播路由..因為prefix|~mask就是計算主機部分都為1的情況. //如果前面的ifa->ifa_broadcast為0的話,那麼這裡就可以根據ip位址來添加廣播路由,那如果設定了ifa->ifa_broadcast,并且 //和prefix|~mask相等..那這裡相當于沒有添加路由,這也是為什麼使用者空間擷取到的type=25消息隻有4條. fib_magic(RTM_NEWROUTE, RTN_BROADCAST, prefix|~mask, 32, prim); } } }    

繼續閱讀