zoukankan      html  css  js  c++  java
  • linux netlink机制

    特点

    1. netlink用于内核和用户空间传输信息。对于用户空间进程,我们可以直接使用socket api 来完成。
    2. netlink 是面向数据报的

    相关函数

    #include <asm/types.h>
    #include <sys/socket.h>
    #include <linux/netlink.h>
    
    netlink_socket = socket(AF_NETLINK, socket_type, netlink_family);
    
    1. socket_type

      取值可以是SOCK_DGRAM或者SOCK_RAW,但是netlink并不区分这个。

    2. netlink_family

      取值有很多,具体参见man 7 netlink。这里我们只记录一个NETLINK_KOBJECT_UEVENT,当我们需要获取内核device的相关信息时(比如tf卡热插拔),就可以设置该协议。

    相关结构体

    1. struct msghdr

      struct iovec {                    /* Scatter/gather array items */
          void  *iov_base;              /* Starting address */
          size_t iov_len;               /* Number of bytes to transfer */
      };
      
      struct msghdr {
          void         *msg_name;       /* optional address */
          socklen_t     msg_namelen;    /* size of address */
          struct iovec *msg_iov;        /* scatter/gather array */
          size_t        msg_iovlen;     /* # elements in msg_iov */
          void         *msg_control;    /* ancillary data, see below */
          size_t        msg_controllen; /* ancillary data buffer len */
          int           msg_flags;      /* flags on received message */
      };
      

      用过recvmsg/sendmsg函数的对这个一定不陌生,实际上,对于Netlink而言,我们也是使用该函数组来接收发送数据。

      在linux 网络编程中,常用的接收发送函数如下:

      #include <sys/types.h>
      #include <sys/socket.h>
      #include <sys/uio.h>
      
      // used only on a connected socket
      ssize_t send(int sockfd, const void *buf, size_t len, int flags);       
      ssize_t recv(int sockfd, void *buf, size_t len, int flags);
      
      // used only on a connected socket
      ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
      ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
      
      //whether or not it is connection-oriented
      ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                     const struct sockaddr *dest_addr, socklen_t addrlen);
      ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                       truct sockaddr *src_addr, socklen_t *addrlen);
      
      //whether or not it is connection-oriented
      ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
      ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
      
      

      这几组函数中,readv/writevsend/recv只能被面向连接的(TCP)socket使用;sendto/recvfrom无论是不是面向连接的socket都能使用;而sendmsg/recvmsg能通过配置struct msghdr来实现前3组函数的所有功能。

      msg_name,取值可以是struct sockaddrstruct sockaddr_instruct sockadd_nlstruct sockaddr_un

      image-20201123130600198

      通常,只有当socket是无连接状态的(比如UDP),才会设置msg_name参数,对于netlink,其取值为sockaddr_nl

      //describes a netlink client in user space or in the kernel.
      struct sockaddr_nl
      {
          sa_family_t nl_family; 	/*该字段总是为AF_NETLINK */
          unsigned short nl_pad; 	/* 目前未用到,填充为0*/
          __u32 nl_pid; 			/* process pid */
          __u32 nl_groups; 		/* multicast groups mask */
      };
      
      • nl_pid

        单播地址,设置为0表示消息destination是内核;否则,通常设置为消息目标进程的进程ID。当一个进程使用多个netlink socket,需要应用程序保证nl_pid的唯一,或者将其设置为0,有内核去分配一个独一无二的值。

      • nl_groups

        多播地址掩码。没用到过!用到再补充,设置为0即可。。。

      msg_iov,消息内容。对于netlink,其实际内容由nlmsghdr填充。。。

      msg_control,指向其他协议控制相关消息或其他辅助数据的缓冲区。其实际类型通常是cmsghdr

    2. struct nlmsghdr

      struct nlmsghdr {
      	__u32 nlmsg_len;    /* Length of message including header. */
      	 __u16 nlmsg_type;   /* Type of message content. */
      	__u16 nlmsg_flags;  /* Additional flags. */
      	__u32 nlmsg_seq;    /* Sequence number. */
      	__u32 nlmsg_pid;    /* PID of the sending process. */
      };
      
      • nlmsg_type

        NLMSG_NOOP message is to be ignored

        NLMSG_ERROR message signals an error and the payload contains an nlmsgerr structure。

        NLMSG_DONE message terminates a multipart message

      • nlmsg_flags

        image-20201123135422694

    3. struct cmsghdr

      关于这些宏的使用方式可以看实例。

      #include <sys/socket.h>
      //返回msghdr中的第一个 cmsghdr的地址
      struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *msgh);
      //返回msghdr中的第一个 cmsghdr的地址
      struct cmsghdr *CMSG_NXTHDR(struct msghdr *msgh, struct cmsghdr *cmsg);
      size_t CMSG_ALIGN(size_t length);
      size_t CMSG_SPACE(size_t length);
      size_t CMSG_LEN(size_t length);
      unsigned char *CMSG_DATA(struct cmsghdr *cmsg);
      
      struct cmsghdr {
      	socklen_t     cmsg_len;     /* data byte count, including hdr */
      	int           cmsg_level;   /* originating protocol */
      	int           cmsg_type;    /* protocol-specific type */
      /* followed by unsigned char cmsg_data[]; */
       };
      

      msg_flags,通常设置为0,取值见man手册。

      image-20201123133400893

    使用示例

    NETLINK_KOBJECT_UEVENT为例。

    1. 创建netlink socket。

      int uevent_open_socket(int buf_sz, bool passcred)
      {
          struct sockaddr_nl addr;
          int on = passcred;
          int s;
          
          memset(&addr, 0, sizeof(addr));
          addr.nl_family = AF_NETLINK;
          addr.nl_pid = getpid();
          addr.nl_groups = 0xffffffff;	//表示接受所有的广播消息???
      
          s = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT);
          if(s < 0)
              return -1;
      
          /* buf_sz should be less than net.core.rmem_max for this to succeed */
          if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &buf_sz, sizeof(buf_sz)) < 0) {
              close(s);
              return -1;
          }
      	// 后面会看到的
          setsockopt(s, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));
      
          if(bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
              close(s);
              return -1;
          }
          return s;
      }
      
    2. 接收netlink消息

      ssize_t uevent_kernel_recv(int socket, void *buffer, size_t length, bool require_group, uid_t *uid)
      {
          struct iovec iov = { buffer, length };
          struct sockaddr_nl addr;
          char control[CMSG_SPACE(sizeof(struct ucred))];
          struct msghdr hdr = {
              &addr,
              sizeof(addr),
              &iov,
              1,
              control,
              sizeof(control),
              0,
          };
      
          *uid = -1;
          ssize_t n = recvmsg(socket, &hdr, 0);
          if (n <= 0) {
              return n;
          }
      
          struct cmsghdr *cmsg = CMSG_FIRSTHDR(&hdr);
          if (cmsg == NULL || cmsg->cmsg_type != SCM_CREDENTIALS) {
              /* ignoring netlink message with no sender credentials */
              goto out;
          }
      	
          // setsockopt(s, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));
          struct ucred *cred = (struct ucred *)CMSG_DATA(cmsg);
          *uid = cred->uid;
          if (cred->uid != 0) {
              /* ignoring netlink message from non-root user */
              goto out;
          }
      
          if (addr.nl_pid != 0) {
              /* ignore non-kernel */
              goto out;
          }
          if (require_group && addr.nl_groups == 0) {
              /* ignore unicast messages when requested */
              goto out;
          }
      
          return n;
      
      out:
          /* clear residual potentially malicious data */
          bzero(buffer, length);
          errno = EIO;
          return -1;
      }
      

    此实例摘自android libcutils 库。通过这两个函数,我们基本就能知道如何使用netlink了。。。

    但是不知道为啥uevent_kernel_recv接收数据时,并未使用nlmsghdr结构体。。。

    nlmsghdr使用示例:

    int len;
    char buf[4096];
    struct iovec iov = { buf, sizeof(buf) };
    struct sockaddr_nl sa;
    struct msghdr msg;
    struct nlmsghdr *nh;
    
    msg = { (void *)&sa, sizeof(sa), &iov, 1, NULL, 0, 0 };
    len = recvmsg(fd, &msg, 0);
    
    for (nh = (struct nlmsghdr *) buf; NLMSG_OK (nh, len);
    	nh = NLMSG_NEXT (nh, len)) {
    	/* The end of multipart message. */
    	if (nh->nlmsg_type == NLMSG_DONE)
    		return;
    
    	if (nh->nlmsg_type == NLMSG_ERROR)
    	/* Do some error handling. */
    		...
    		/* Continue with parsing payload. */
    		...
    }
    

    补充: 走读了udev模块的kobject_uevent_env方法,似乎没看到和 nlmsghdr的处理。

  • 相关阅读:
    ssh整合思想 Spring与Hibernate和Struts2的action整合 调用action添加数据库 使用HibernateTemplate的save(entity)方法 update delete get 等方法crud操作
    ssh整合思想 Spring与Hibernate和Struts2的action整合 调用action添加数据库 使用HibernateTemplate的save(entity)方法 ognl.MethodFailedException:Method"execute" failed for ssh包合集下载
    ssh整合思想 Spring与Hibernate的整合 项目在服务器启动则自动创建数据库表
    ssh整合思想 Spring与Hibernate的整合ssh整合相关JAR包下载 .MySQLDialect方言解决无法服务器启动自动update创建表问题
    将博客搬至CSDN
    Re-Order Oracle columns of tables
    在Word中的方框里打对勾
    Interpreting java.lang.NoSuchMethodError message
    TIMEZONE FOR UAE/BH/RU/PL
    申论写作五大标准“配件”
  • 原文地址:https://www.cnblogs.com/liutimo/p/14021871.html
Copyright © 2011-2022 走看看