Netlink套接字

来源:互联网 发布:淘宝 一千零一夜 编辑:程序博客网 时间:2024/04/30 05:51

    Netlink套接字是用于用户与内核间的通信,类似于socket的客户/服务端通信程序,内核与用户之间的通信是全双工的,也即通信既可以从用户端发起,又可以从内核发起,并且netlink可以自定义协议进行通信,但这时需要在内核中相应位置(具体位置忘了)自己定义相应的协议内容,并在netlink头文件定义相应协议的名字。一般使用系统已经定义好的常用协议NETLINK_ROUTE,NETLINK_FIREWALL,NETLINK_APRD,NETLINK_ROUTE6_FW等,这些协议可以直接调用,用于与内核交互,功能强大。

    Netlink的使用主要要熟悉netlink消息的封装与解封装,与普通socket不同,netlink套接字的封装与解封装需要自己进行 ,这就需要熟悉netlink套接字消息的结构,熟练掌握一些宏的应用(这些都是有套路的,需要花时间来掌握)。

    首先来看netlink socket支持的一些协议:

  • NETLINK_ROUTE:用户空间路由damon,如BGP,OSPF,RIP和内核包转发模块的通信信道。用户空间路由damon通过此种netlink协议类型更新内核路由表
  • NETLINK_FIREWALL:接收IPv4防火墙代码发送的包
  • NETLINK_NFLOG:用户空间iptable管理工具和内核空间Netfilter模块的通信信道
  • NETLINK_ARPD:用户空间管理arp表
这些不同的协议对应与内核不同的交互,比如NETLINK_ROUTE就是通过与内核交互获取路由信息,这些协议可以直接调用使用。

1.首先创建一个套接字

      int socket(int domain,int type, int protocol)

domain指代地址族,填AF_NETLINK,套接字类型不是SOCK_RAW就是SOCK_DGRAM,因为netlink是一个面向数据报的服务。

protocol选择该套接字使用那种netlink特征。以下是几种预定义的协议类型:NETLINK_ROUTE,NETLINK_FIREWALL,NETLINK_APRD,NETLINK_ROUTE6_FW。你也可以非常容易的添加自己的netlink协议。

2.绑定套接字与地址

      bind(fd, (struct sockaddr*)&nladdr, sizeof(nladdr));
关于nladdr,类似于sockaddr_in,是netlink套接字中特有的地址类型

     struct sockaddr_nl
     {
       sa_family_t    nl_family;  /* AF_NETLINK   */
       unsigned short nl_pad;     /* zero         */
       __u32          nl_pid;     /* process pid */
       __u32          nl_groups;  /* mcast groups mask */
     } nladdr;
nl_pid为0时表示内核态,若是用户态,一般使用nl_pid=getpid(),即当前进程号,当一个进程中的不同线程需要同一个netlink协议多个netlink套接字,可使用
 pthread_self() << 16 | getpid();通过这种方法,同一个进程中的不同线程可以有同一种netlink协议类型的netlink套接字
字段 nl_pad当前没有使用,因此要总是设置为 0

字段 nl_groups用于指定多播组,若用于单播则置为0

字段 nl_family必须设置为 AF_NETLINK或着 PF_NETLINK


3.发送netlink消息

使用netlink套接字和内核进行交互时,要向内核发送消息,一般使用sendmsg发送消息,recvmsg接收消息,主要是消息的封装和解封装

对于netlink消息,有一个特定的消息头,它也被称为netlink控制块

     struct nlmsghdr
     {
       __u32 nlmsg_len;   /* Length of message */
       __u16 nlmsg_type;  /* Message type*/
       __u16 nlmsg_flags; /* Additional flags */
       __u32 nlmsg_seq;   /* Sequence number */
       __u32 nlmsg_pid;   /* Sending process PID */
     }nlh;

字段 nlmsg_len指定消息的总长度,包括紧跟该结构的数据部分长度以及该结构的大小,字段 nlmsg_type用于应用内部定义消息的类型,它对 netlink内核实现是透明的,因此大部分情况下设置为 0,字段 nlmsg_flags 用于设置消息标志,可用的标志包括:

标志NLM_F_REQUEST用于表示消息是一个请求,所有应用首先发起的消息都应设置该标志。

标志NLM_F_MULTI用于指示该消息是一个多部分消息的一部分,后续的消息可以通过宏NLMSG_NEXT来获得。

宏NLM_F_ACK表示该消息是前一个请求消息的响应,顺序号与进程ID可以把请求与响应关联起来。

标志NLM_F_ECHO表示该消息是相关的一个包的回传。

标志NLM_F_ROOT被许多 netlink协议的各种数据获取操作使用,该标志指示被请求的数据表应当整体返回用户应用,而不是一个条目一个条目地返回。有该标志的请求通常导致响应消息设置 NLM_F_MULTI标志。注意,当设置了该标志时,请求是协议特定的,因此,需要在字段 nlmsg_type 中指定协议类型。

标志 NLM_F_MATCH表示该协议特定的请求只需要一个数据子集,数据子集由指定的协议特定的过滤器来匹配。

标志 NLM_F_ATOMIC指示请求返回的数据应当原子地收集,这预防数据在获取期间被修改。

标志 NLM_F_DUMP未实现。

标志 NLM_F_REPLACE用于取代在数据表中的现有条目。

标志 NLM_F_EXCL_用于和 CREATE和 APPEND配合使用,如果条目已经存在,将失败。

标志 NLM_F_CREATE指示应当在指定的表中创建一个条目。

标志 NLM_F_APPEND指示在表末尾添加新的条目。

内核需要读取和修改这些标志,对于一般的使用,用户把它设置为 0就可以,只是一些高级应用(如 netfilter和路由 daemon需要它进行一些复杂的操作),字段 nlmsg_seq和 nlmsg_pid用于应用追踪消息,前者表示顺序号,后者为消息来源进程 ID。


结构 struct iovec用于把多个消息通过一次系统调用来发送

       struct iovec iov;
      iov.iov_base = (void *)nlh;    
       iov.iov_len = nlh->nlmsg_len;用于发送消息的msg,真正发送的是msg,前面都是为msg的封装做准备工作      
        struct msghdr msg;
msg.msg_name = (void *)&(nladdr); 这个nladdr是目的地址内核的nladdr,ni_pid=0,
        msg.msg_namelen = sizeof(nladdr);                msg.msg_iov = &iov;    
        msg.msg_iovlen = 1;

msg封装完毕后,即可发送出去

        sendmsg(fd, &msg, 0);当信息发送出去,内核会给出回应,此时用户用recvmsg接收消息,并解封装,获取自己想要的消息(解封装有一些固定的流程,尤其是宏的调用)




接收部分:

   1:  struct sockaddr_nl nladdr;
   2:  struct msghdr msg;
   3:  struct iovec iov;
   4:   
   5:  iov.iov_base = (void *)nlh;            接收时之前发送的消息头要清空
   6:  iov.iov_len = MAX_NL_MSG_LEN;
   7:  msg.msg_name = (void *)&(nladdr);      这个nladdr是发送时的nladdr
   8:  msg.msg_namelen = sizeof(nladdr);
   9:   
  10:  msg.msg_iov = &iov;
  11:  msg.msg_iovlen = 1;
  12:  recvmsg(fd, &msg, 0);接下来是对msg的解封装:以下是对通过netlink获取主路由网关的源码:

    #include <arpa/inet.h>
    #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>  
    #include <netinet/in.h>
    #include <linux/rtnetlink.h>
    #include <net/if.h>          
    
    #define MAX_PAYLOAD 1024 // maximum payload size  
      
    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;
    
    struct route_info{   
                          u_int dstAddr;   
              u_int srcAddr;   
              u_int gateWay;   
              char ifName[IF_NAMESIZE];   
                         };

        int len;  
        struct rtmsg *rtm;  
        struct rtattr *tb [RTA_MAX + 1];  
        u_char flags = 0;  
        int table;  
        void *dest;  
        void *gate;  
        struct rtattr *rta;
        int max;
        char gateway[60]={0};
        struct in_addr destaddr;
        struct in_addr gateaddr;
        int rtlen;
        struct rtattr *rtAttr;
        struct route_info rtInfo;
        sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);  
        if(sock_fd == -1){  
            printf("error getting socket: %s", strerror(errno));  
            return -1;  
        }  
      

        bzero(&gateaddr,sizeof(gateaddr));
        // To prepare binding  
        memset(&msg,0,sizeof(msg));  
        memset(&src_addr, 0, sizeof(src_addr));  
        src_addr.nl_family = AF_NETLINK;  
        src_addr.nl_pid = getpid();             //         用户进程id                   src_addr源地址的设定
        src_addr.nl_groups = 0;                //         单播模式  
      
        retval = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));     //  netlink套接字与源地址绑定  
        if(retval < 0)
    {  
          printf("bind failed: %s", strerror(errno));  
          close(sock_fd);  
          return -1;  
        }  
      
        // To prepare recvmsg  
      
        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的定义
        dest_addr.nl_family = AF_NETLINK;  
        dest_addr.nl_pid = 0;                                                //pid=0表示内核
        dest_addr.nl_groups = 0;  
      
        nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);                          // nlmsghdr的封装,即netlink消息头
        nlh->nlmsg_type=RTM_GETROUTE;  
        nlh->nlmsg_pid = getpid();                                     
        nlh->nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;      //设置netlink消息头的标志位
 
      
        iov.iov_base = (void *)nlh;                                         //iovec的封装,base部分指向前面的nlmsghdr
        iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);  
        // iov.iov_len = nlh->nlmsg_len;  
      
        memset(&msg, 0, sizeof(msg));  
         
        msg.msg_name = (void *)&dest_addr;                                    //用于发送的msg,放入dest_addr
        msg.msg_namelen = sizeof(dest_addr);        
        msg.msg_iov = &iov;                                                  //放入iovec
        msg.msg_iovlen = 1;  
      

    
        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));                           //清空nlmsghdr
 //     printf("waiting received!\n");  
        // Read message from kernel  
      
        while(1){  

            state = recvmsg(sock_fd, &msg, 0);                           //收到内核发送回用户的路由消息
            if(state<0)
            {
                printf("state<1");
                return;
            }


        rtm =(struct rtmsg*) NLMSG_DATA (nlh );                      //指向有用数据部分  

        if((rtm->rtm_family != AF_INET) || (rtm->rtm_table != RT_TABLE_MAIN))         //判断收到的类型和路由表是否为主路由表   
                          return;  

        rtlen=RTM_PAYLOAD(nlh);
              
        rtAttr=(struct rtattr *)RTM_RTA(rtm);             
        
            for(;RTA_OK(rtAttr,rtlen);rtAttr=RTA_NEXT(rtAttr,rtlen))                     //for循环固定方法读取收到的信息,如果有网关的信息打印出来
             {
            switch(rtAttr->rta_type)                                        
            {     //RTA_NEXT用于轮循所有路由信息,比如先网关信息,RTA_NEXT之后就会查比如设备名,直到查完这个路由表所有信息再跳出for循环
                case RTA_OIF:
                if_indextoname(*(int *)RTA_DATA(rtAttr),rtInfo.ifName);
    //            printf("%s\n",rtInfo.ifName);    
                break;
                case RTA_GATEWAY:
                rtInfo.gateWay=*(u_int *)RTA_DATA(rtAttr);
                    gateaddr.s_addr=rtInfo.gateWay;

                                sprintf(gateway,"gateway:%s", (char *)inet_ntoa(gateaddr));

                                printf("%s\n",gateway);

                                break;
                default:
                break;
            }

        }
    



          
 
        }  
      
        close(sock_fd);  
      
        return 0;  
    } 






0 0
原创粉丝点击