netlink套接字

netlink套接字1 netlink簇2 netlink数据结构2.1 消息地址:struct sockaddr_nl2.2 消息头 nlmsghdr参数:nlmsg_flags参数:type2.3 有效载荷属性nlattr2.4 属性期望2.5 struct netlink_kernel_cfg成员:API1. 创建1.1 用户-socket()1.2 内核-netlink_kernel_create()示例:2. 发送2.1 单播 netlink_unicast2.2 组播 netlink_broadcast消息头处理函数1 nlmsg_put()消息头处理宏1 NETLINK_CB2 NLMSG_DATA附录netlink协议清单

应用

(1)实现用户-内核空间的通信

(2)内核空间两个进程之间的通信。

1 netlink簇

用户和内核间通信

(1) /proc-单向:虚拟文件系统,用户获取内核信息,输出数据

(2) ioctl-单向:不能发送异步信息,用户向内核传送命令

(3)netlink-双向:可以启动传输,而不是仅限于响应用户空间请求而返回信息

Netlink协议(RFC3549)

2 netlink数据结构

2.1 消息地址:struct sockaddr_nl

netlink的通信地址,跟struct sockaddr_in 类似

(struct sockaddr_in 跟struct addr 兼容)

 nl_family:始终为AF_NETLINK
 nl_pad:总是为0
 nl_pid:单播地址。对于内核netlink套接字,该值为0。
                 用户空间:当前进程id
 ​
 nl_groups:组播组,或组播组掩码。为0表示不加入任何多播组,最多支持32个多播组
         若用户空间的进程希望加入某个多播组(multicast group),则必须执行bind()系统调用

对比:

2.2 消息头 nlmsghdr

也被称为控制块

 nlmsg_length :消息长度:紧跟该结构的数据部分长度以及该结构的大小
 nlmsg_type:应用内部定义消息的类型,对netlink内核实现是透明的,因此大部分情况下设置为0
 nlmsg_flags:设置消息标志
 nlmsg_seq:消息序列号,用以将消息排队,字段可选
 nlmsg_pid:消息来源进程 ID

参数:nlmsg_flags

内核需要读取和修改这些标志,对于一般的使用,用户把它设置为 0 就可以,只是一些高级应用(如 netfilter 和路由 daemon 需要它进行一些复杂的操作)

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

参数:type

 NLMSG_NOOP:不执行任何动作,必须将该消息丢弃;
 NLMSG_ERROR:消息发生错误;
 NLMSG_DONE:标识分组消息的末尾;
 NLMSG_OVERRUN:缓冲区溢出,表示某些消息已经丢失。
 NLMSG_MIN_TYPEK:预留

示例:

 struct msghdr msg;//消息头
 memset(&msg, 0, sizeof(msg));//清空
 msg.msg_name = (void *)&(nladdr);//存储地址信息
 msg.msg_namelen = sizeof(nladdr);//设置地址大小
 ​
 {
 /*初始化一个strcut nlmsghdr结构存,
 nlmsghdr为netlink socket自己的消息头部,
 并使iov->iov_base指向在这个结构*/
 ​
 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;
     
 iov.iov_base = (void *)nlhdr;
 iov.iov_len = nlh->nlmsg_len;
 }
 ​
 msg.msg_iov = &iov;//数据包
 msg.msg_iovlen = 1;
 fd=socket(AF_NETLINK, SOCK_RAW, netlink_type);
 sendmsg(fd,&msg,0)

2.3 有效载荷属性nlattr

紧跟在报头后面的是有效载荷。

有效载荷格式:类型-长度-值(TLV)表示属性

参数:

nla_len: 属性的长度

nla_type: 属性的类型

注意:netlink属性必须与4字节边界对齐(NLA_ALIGNTO)

2.4 属性期望

用法:对象数组,数组将属性号作为索引。

对于每个属性:如果nla_policy->len=0, 不执行有效性检查

如果nla_policy->type为字符串,则len应为字符串的最大长度(不包括末尾的NULL字节)

2.5 struct netlink_kernel_cfg

该结构包含用于创建netlink套接字的可选参数

成员:

  • groups:组编号

    sockaddr_nl结构的成员nl_groups最多只能设置32个组播组。可使用套接字选项NETLINK_ADD_MEMBERSHIP /NETLINK_DROP_MEMBERSHIP来加入或退出组播组

  • flags: NL_CFG_F_NONROOT_RECV (非root用户可绑定到组播组) 或NL_CFG_F_NONROOT_SEND(非root用户可发送组播)

  • input:用于指定回调函数。用于处理用户空间发送到内核方向的数据。内核接收到数据包后会传给input函数。接收一个sk_buff结构,数据包含一个nlmsghdr协议头

  • cb_mutex: 互斥锁,可选,默认cb_def_mutex

API

1. 创建

1.1 用户-socket()

 int socket(int domain, int type, int protocol);
 参数:domain: AF_NETLINK或者PF_NETLINK
      type:SOCK_RAW或SOCK_DGRAM (面向数据报的服务)              
      protocol:  NETLINK_ROUTE,NETLINK_FIREWALL,NETLINK_APRD,NETLINK_ROUTE6_FW。  

1.2 内核-netlink_kernel_create()

参数:

  • net :网络命名空间

  • uint:netlink协议(总数不超过MAX_LINKS(32))

  • cfg: 引用netlink_kernel_cfg参数的引用,该结构包含用于创建netlink套接字的可选参数

返回值:

成功:返回一个sock结构

失败:NULL

示例:

2. 发送

来自用户进程的netlink消息会以netlink_unicast单播或者netlink_brocast组播方式进行传递。

2.1 单播 netlink_unicast

应用场景:

  1. 内核 ----单播---》用户进程

  2. 用户进程 ----单播---》kernel

  3. 用户进程a ---->单播---》用户进程b

 /* 发送netlink单播消息,会自动释放skb 
  * ssk:源sock结构
  * skb:属于发送方的承载了netlink消息的skb
  * portid:目的单播地址
  * nonblock: 1.非阻塞调用 2 阻塞调用
  */
 int netlink_unicast(struct sock *ssk, struct sk_buff *skb,
             u32 portid, int nonblock)
 {
     struct sock *sk;
     int err;
     long timeo;
     skb = netlink_trim(skb, gfp_any());// 重新调整skb数据区大小
     timeo = sock_sndtimeo(ssk, nonblock);// 计算发送超时时间(如果是非阻塞调用,则返回0)
 retry:
     // 根据源sock结构和目的单播地址,得到sock结构
     sk = netlink_getsockbyportid(ssk, portid);
     // 如果该目的netlink套接字属于则进入第2种情况分支
     if (IS_ERR(sk)) {
         kfree_skb(skb);
         return PTR_ERR(sk);
     }
     if (netlink_is_kernel(sk))
         return netlink_unicast_kernel(sk, skb, ssk);
 ​
     if (sk_filter(sk, skb)) {
         err = skb->len;
         kfree_skb(skb);
         sock_put(sk);
         return err;
     }
 ​
     err = netlink_attachskb(sk, skb, &timeo, ssk);
     if (err == 1)
         goto retry;
     if (err)
         return err;
 ​
     return netlink_sendskb(sk, skb);
 }
 EXPORT_SYMBOL(netlink_unicast);

2.2 组播 netlink_broadcast

内核定义netlink多播群组的目的是传出特定种类事件的通知信息

消息头处理函数

1 nlmsg_put()

添加新的netlink信息到skb

消息头处理宏

 #define NLMSG_ALIGNTO   4U   
 #define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
 //用于得到不小于len,且字节对齐的最小数值
 ​
 #define NLMSG_HDRLEN     ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
 //最大允许长度
 ​
 #define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)
 //用于计算数据部分长度为len时,实际消息的长度,一般用于分配消息缓存
 ​
 #define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
 //返回不小于NLMES_LENGTH(len)且字节对齐的最小数值,也用于分配消息缓存
 ​
 ​
 #define NLMSG_NEXT(nlh,len)  ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
                   (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))
 //得到下一个消息的首地址,同时len也减少为剩余消息的总长度,一般在消息被分成几个部分发送或接受时使用
 ​
 #define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \
                (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \
                (nlh)->nlmsg_len <= (len))
 //判断消息是否有len那么长
 ​
 #define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))
 //用于返回payload的长度

1 NETLINK_CB

 // 用于设置netlink地址消息
 #define NETLINK_CB(skb)     (*(struct netlink_skb_parms*)&((skb)->cb))

消息存储在skb.cb中,消息的结构如下

 struct netlink_skb_parms {
     struct scm_creds    creds;      /* Skb credentials(证书)  */
     __u32           portid;
     __u32           dst_group;
     __u32           flags;
     struct sock     *sk;
 };

2 NLMSG_DATA

 #define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
 //用于取得消息的数据部分的首地址,设置和读取消息数据部分时,需要使用

附录

netlink协议清单

 路由 daemon(NETLINK_ROUTE)
 1-wire 子系统(NETLINK_W1)
 用户态 socket 协议(NETLINK_USERSOCK),
 防火墙(NETLINK_FIREWALL),
 socket 监视(NETLINK_INET_DIAG),
 netfilter 日志(NETLINK_NFLOG)
 ipsec 安全策略(NETLINK_XFRM),
 SELinux 事件通知(NETLINK_SELINUX),
 iSCSI 子系统(NETLINK_ISCSI),
 进程审计(NETLINK_AUDIT),
 转发信息表查询(NETLINK_FIB_LOOKUP),
 netlink connector(NETLINK_CONNECTOR),
 netfilter 子系统(NETLINK_NETFILTER),
 IPv6 防火墙(NETLINK_IP6_FW),
 DECnet 路由信息(NETLINK_DNRTMSG),
 内核事件向用户态通知(NETLINK_KOBJECT_UEVENT),
 通用 netlink(NETLINK_GENERIC

版权声明:本文为qq_40844674原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。