惊鸿浪子

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

1:参考网址

2:netlink相关socket API

      netlink用于程序与内核模块之间进行通信。用户态使用netlink与内核态使用netlink方式不同,Linux container在网络管理这一块处于用户空间。netlink 在用户态的API与tcp/ip通信使用的socket api 类似,主要api为socket()、bind()、sendmsg()、recvmsg().

  • socket()

      使用netlink需要包含的头文件为linux/netlink.h和sys/socket.h(socket api头文件)。创建socket函数。

int socket(AF_NETLNK,SOCK_RAW,netlink_type)

    创建socket时地址族选择AF_NETLINK或者PF_NETLINK。第二个参数还可以是SOCK_DGRAM。第三个参数指定netlink协议类型。socket所有的地址族有以下所有类型:
AF_INET               IPv4协议族
AF_INET6              IPv6协议族
AF_LOCAL              Unix域协议
AF_ROUTE              路由套接字
AF_KEY                密钥套接字

     有时AF也用PF代替,PF代表 Protocol Family(协议族),AF代表Address Familiy(地址族)。

     第二个参数指明通信字节流类型,其取值如SOCK_STREAM(tcp方式),SOCK_DGRAM(udp方式)、SOCK_RAW(原始套接口)、SOCK_PACKET(支持数据链路访问)。

     netlink_type类型如下:

#define NETLINK_ROUTE           0       /* Routing/device hook                          */
#define NETLINK_W1              1       /* 1-wire subsystem                             */
#define NETLINK_USERSOCK        2       /* Reserved for user mode socket protocols      */
#define NETLINK_FIREWALL        3       /* Firewalling hook                             */
#define NETLINK_INET_DIAG       4       /* INET socket monitoring                       */
#define NETLINK_NFLOG           5       /* netfilter/iptables ULOG */
#define NETLINK_XFRM            6       /* ipsec */
#define NETLINK_SELINUX         7       /* SELinux event notifications */
#define NETLINK_ISCSI           8       /* Open-iSCSI */
#define NETLINK_AUDIT           9       /* auditing */
#define NETLINK_FIB_LOOKUP      10
#define NETLINK_CONNECTOR       11
#define NETLINK_NETFILTER       12      /* netfilter subsystem */
#define NETLINK_IP6_FW          13
#define NETLINK_DNRTMSG         14      /* DECnet routing messages */
#define NETLINK_KOBJECT_UEVENT  15      /* Kernel messages to userspace */
#define NETLINK_GENERIC         16

     在linux container中,关于netlink中,使用的netlink协议类型为NETLINK_ROUTE。

  • bind()

      bind函数是将本地协议地址赋给一个套接字。

int bind(int sockfd,const struct sockaddr *myaddr,socklen_t addrlen)

     在TCP/IP模型中, sockfd为创建的套接字描述符,myaddr为要绑定到套接字的地址,addrlen为struct sockaddr长度。bind函数把一个特定的IP地址捆绑到它的套接字上,这个IP地址必须属于主机所在的网络接口之一。即myaddr为本地源IP地址和所用的端口。针对TCP客户端,这指定了在该套接字上发送的IP数据报指派了源IP地址。对于TCP服务器,这限定了该套接字只接收那些目的地为这个IP地址的客户连接。

      针对netlink,bind函数的myaddr为struct sockaddr_nl。struct sockaddr_nl结构体如下

struct sockaddr_nl
{
  sa_family_t    nl_family;  
  unsigned short nl_pad;
  __u32          nl_pid;
  __u32          nl_groups;
};

     字段nl_family必须设置为AF_NETLINK或者PF_NETLINK。字段nl_pad当前没有用,设置为0。nl_pid为0时表示接收或者发送消息的进程pid。当nl_pid为0时表示处理消息的为内核。字段nl_groups用于指定多播组,bind 函数用于把调用进程加入到该字段指定的多播组,如果设置为 0,表示调用者不加入任何多播组。

     在进行bind函数调用时,myaddr为本地地址,其中,myaddr.nl_pid为本地进程pid,myaddr.nl_groups为0,表示本地进程不加入任何多播组,否则本地进程加入多播组。

  • 发送消息、接收消息
int sendmsg(int s, const struct msghdr *msg, unsigned int flags);
int recvmsg(int s, struct msghdr *msg, unsigned int flags);

      其中s为socket描述符,msg为消息,flags为标志,

      消息头struct nlmsghdr

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 */
};

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

/* Flags values */
#define NLM_F_REQUEST           1       /* It is request message.       */
#define NLM_F_MULTI             2       /* Multipart message, terminated by NLMSG_DONE */
#define NLM_F_ACK               4       /* Reply with ack, with zero or error code */
#define NLM_F_ECHO              8       /* Echo this request            */
/* Modifiers to GET request */
#define NLM_F_ROOT      0x100   /* specify tree root    */
#define NLM_F_MATCH     0x200   /* return all matching  */
#define NLM_F_ATOMIC    0x400   /* atomic GET           */
#define NLM_F_DUMP      (NLM_F_ROOT|NLM_F_MATCH)
/* Modifiers to NEW request */
#define NLM_F_REPLACE   0x100   /* Override existing            */
#define NLM_F_EXCL      0x200   /* Do not touch, if it exists   */
#define NLM_F_CREATE    0x400   /* Create, if it does not exist */
#define NLM_F_APPEND    0x800   /* Add to end of list           */

标志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 指示在表末尾添加新的条目。

      字段nlmsg_seq和nlmsg_pid用于追踪消息,nlmsg_seq表示顺序号,nlmsg_pid表示消息来源进程id。

      消息结构体struct msghdr

struct msghdr {
    void         *msg_name;
    socklen_t    msg_namelen;
    struct iovec *msg_iov;
    size_t       msg_iovlen;
    void         *msg_control;
    size_t       msg_controllen;
    int          msg_flags;
};

       msg_name和msg_namelen表示套接口地址成员,msg_name表示发送消息的目标地址,接收消息的源地址,msg_namelen表示地址长度。msg_iov和msg_iovlen表示I/O向量引用。其中msg_iov一般指向消息头,这些成员指定了我们的I/O向量数组的位置以及他包含多少项。msg_iov成员指向一个struct iovec数组。I/O向量指向我们的缓冲区。成员msg_iov指明了在我们的I/O向量数组中有多少元素.msg_iov指向了一个具体的消息,包括消息头nlmsghdr和消息头后紧跟的消息数据部分。

      成员msg_control与msg_controllen,这些成员指向了我们附属数据缓冲区并且表明了缓冲区大小。msg_control指向附属数据缓冲区,而msg_controllen指明了缓冲区大小。成员msg_flags为接收消息标记位,这个成员用于接收特定的标记位(他并不用于sendmsg)。msg_flags值为

MSG_EOR    当接收到记录结尾时会设置这一位。这通常对于SOCK_SEQPACKET套接口类型十分有用。
MSG_TRUNC    这个标记位表明数据的结尾被截短,因为接收缓冲区太小不足以接收全部的数据。
MSG_CTRUNC    这个标记位表明某些控制数据(附属数据)被截短,因为缓冲区太小。
MSG_OOB        这个标记位表明接收了带外数据。
MSG_ERRQUEUE    这个标记位表明没有接收到数据,但是返回一个扩展错误。

       其他结构体 struct iovec

struct iovec {
void * iov_base ;    /*pointer to data */
size_t iov_len    ;    /*length of data */
}

3. 发送和接收消息的具体流程

     消息宏

#define NLMSG_ALIGNTO   4
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )

      宏NLMSG_ALIGN(len)用于得到不小于len且字节对齐的最小数值。若某个对象的长度为18,那么在为其分配空间时,通过NLMSG_ALIGN宏就可以计算出最接近其的4的倍数为((18+4-1) & ~(3)) = 20,这样便为其申请/分配20字节空间。这是32位微控制器/微处理器中为了防止非对齐操作产生Exception而添加的保护措施。

#define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(sizeof(struct nlmsghdr)))

      宏NLMSG_LENGTH(len)用于计算数据部分长度为len时实际的消息长度。即数据部分长度和消息头长度

#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))

      宏NLMSG_SPACE(len)返回不小于NLMSG_LENGTH(len)且字节对齐的最小数值

#define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))

    宏NLMSG_DATA(nlh)用于取得消息的数据部分的首地址,设置和读取消息数据部分时需要使用该宏。

#define NLMSG_NEXT(nlh,len)      ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
                      (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))

      宏NLMSG_NEXT(nlh,len)用于得到下一个消息的首地址,同时len也减少为剩余消息的总长度,该宏一般在一个消息被分成几个部分发送或接收时使用

#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \
                           (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \
                           (nlh)->nlmsg_len <= (len))

     宏NLMSG_OK(nlh,len)用于判断消息是否有len这么长。

#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))

    宏NLMSG_PAYLOAD(nlh,len)用于返回payload的长度。

     发送和接收消息过程

无标题

      发送消息

     发送消息,设置消息头,以及消息头后的数据部分

#define MAX_MSGSIZE 1024
char buffer[] = "An example message";
struct nlmsghdr nlhdr;
nlhdr = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_MSGSIZE));
strcpy(NLMSG_DATA(nlhdr),buffer);
nlhdr->nlmsg_len = NLMSG_LENGTH(strlen(buffer));
nlhdr->nlmsg_pid = getpid(); /* self pid */
nlhdr->nlmsg_flags = 0;

2

      发送消息时,需要设置指向消息的结构体struct msghdr。

/*设置目标地址*/
struct sockaddr_nl dest_addr;
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = 0;  
dest_addr.nl_groups = 0; 
/*设置消息*/
struct iovec iov;
iov.iov_base = (void *)nlhdr;             /*指向消息头*/
iov.iov_len = nlh->nlmsg_len;             /*整个消息长度*/
struct msghdr msg;
msg.msg_iov = &iov;                       /*I/O向量数组开始*/
msg.msg_iovlen = 1;                       /*数组长度为1*/
msg.msg_name = (void *)&(dest_addr);      /*设置目标地址*/
msg.msg_namelen = sizeof(dest_addr);      /*目标地址长度*/
     最后发送消息

     sendmsg(fd,&msg,0);

    接收消息

#设置消息头
#define MAX_NL_MSG_LEN 1024
struct nlmsghdr * nlhdr;
nlhdr = (struct nlmsghdr *)malloc(MAX_NL_MSG_LEN);
#设置消息源地址
struct sockaddr_nl src_addr;
memset(&src_addr,0,sizeof(src_addr));
src_addr.nl_famlily=AF_NETLINK;
src_addr.nl_pid=src_pid;     /*对方进程pid*/
src_addr.nl_groups=0;
#设置消息
struct msghdr msg;
struct iovec iov;
iov.iov_base = (void *)nlhdr;
iov.iov_len = MAX_NL_MSG_LEN;
msg.msg_name = (void *)&(src_addr);
msg.msg_namelen = sizeof(src_addr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
recvmsg(fd, &msg, 0);
posted on 2014-12-30 22:03  shithappens  阅读(586)  评论(0编辑  收藏  举报