linux的netlink机制

linux下从内核空间到用户空间的异步通信可以使用到netLink,像设备的路由等信息都是通过该机制实现。

一 netlink的了解

Netlink套接字是用以实现用户进程内核进程通信的一种特殊的进程间通信(IPC) ,也是网络应用程序与内核通信的最常用的接口。

 netlink协议基于BSD socket和AF_NETLINK地址簇(address family),使用32位的端口号寻址(以前称作PID),每个netlink协议(或称作总线,man手册中则称之为netlink family),通常与一个或一组内核服务/组件相关联,如NETLINK_ROUTE用于获取和设置路由与链路信息、NETLINK_KOBJECT_UEVENT用于内核向用户空间的udev进程发送通知等。

Netlink 是一个面向数据包的服务。  SOCK_RAW 和 SOCK_DGRAM 都是 socket_type 的有效值。然而 netlink 协议对数据包 datagram 和原套接字(raw sockets)并不作区分。

netlink具有以下特点:
① 支持全双工、异步通信(当然同步也支持)
② 用户空间可使用标准的BSD socket接口(但netlink并没有屏蔽掉协议包的构造与解析过程,推荐使用libnl等第三方库)
③ 在内核空间使用专用的内核API接口
③ 支持多播(因此支持“总线”式通信,可实现消息订阅)
④ 在内核端可用于进程上下文与中断上下文

一般来说用户空间和内核空间的通信方式有三种:/proc、ioctl、Netlink。而前两种都是单向的,而Netlink可以实现双工通信。

Netlink 相对于系统调用,ioctl 以及 /proc文件系统而言,具有以下优点:

  • netlink使用简单,只需要在include/linux/netlink.h中增加一个新类型的 netlink 协议定义即可,(如 #define NETLINK_TEST 20然后,内核和用户态应用就可以立即通过 socket API 使用该 netlink 协议类型进行数据交换)
  • netlink是一种异步通信机制,在内核与用户态应用之间传递的消息保存在socket缓存队列中,发送消息只是把消息保存在接收者的socket的接收队列,而不需要等待接收者收到消息
  • 使用 netlink 的内核部分可以采用模块的方式实现,使用 netlink 的应用部分和内核部分没有编译时依赖
  • netlink 支持多播,内核模块或应用可以把消息多播给一个netlink组,属于该neilink 组的任何内核模块或应用都能接收到该消息,内核事件向用户态的通知机制就使用了这一特性
  • 内核可以使用 netlink 首先发起会话

二、为AF_NETLINK地址簇添加新协议的方法

要添加的netlink”协议“的说法,要在内核中使用netlink,需要为自己的内核服务添加新的netlink或则在现有的上面修改,现在有两种方法:

① 最开始的方法,使用<net/netlink.h>中的接口,即直接基于nlmsghdr。这是在Linux加入netlink机制之初添加协议的方法。该方法有个限制就是协议的数量不能超过32个,Linux3.10内核已经使用了22个。
② 使用netlink generic。netlink generic是基于第一种方法实现的,协议号为NETLINK_GENERIC。它在NETLINK_GENERIC协议之上提供了多路复用,在其之上添加的新协议称之为Generic Netlink 协议,在没有歧义的情况下,也称作Generic协议或netlink协议。

基本入门了解,而且本文写的是第一种的。

三、NetLink协议基础

netlink的协议头如图:

 netlink协议是面向消息的,要定义自己的协议,需要基于netlink提供的协议头,即struct nlmsghdr。自定义协议按照协议头格式填充协议头内容,并定义自己的playload,通常自定义的协议体包含自定义协议头与额外的属性,netlink提供了一系列的标准方法用于对消息进行打包与拆包。详细见下面的结构体。

二 使用的结构体

用户态应用使用标准的 socket API有sendto(),recvfrom(), sendmsg(), recvmsg()。

1. netlink消息类型

已经有许多内核模块使用netlink 机制,其中驱动模型中使用的uevent 就是基于netlink 实现。目前 netlink 协议族支持32种协议类型,它们定义在 include/uapi/linux/netlink.h 中:

#define NETLINK_ROUTE       0   /* Routing/device hook              */
#define NETLINK_UNUSED      1   /* Unused number                */
#define NETLINK_USERSOCK    2   /* Reserved for user mode socket protocols  */
#define NETLINK_FIREWALL    3   /* Unused number, formerly ip_queue     */
#define NETLINK_SOCK_DIAG   4   /* 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
/* leave room for NETLINK_DM (DM Events) */
#define NETLINK_SCSITRANSPORT   18  /* SCSI Transports */
#define NETLINK_ECRYPTFS    19
#define NETLINK_RDMA        20
#define NETLINK_CRYPTO      21  /* Crypto layer */

#define NETLINK_INET_DIAG   NETLINK_SOCK_DIAG

#define MAX_LINKS 32

2. struct sockaddr_nl结构

Netlink通信跟常用UDP Socket通信类似,struct sockaddr_nl是netlink通信地址,跟普通socket struct sockaddr_in类似。

struct sockaddr_nl {
     __kernel_sa_family_t    nl_family;  /* AF_NETLINK (跟AF_INET对应)*/
     unsigned short  nl_pad;     /* zero */
     __u32       nl_pid;     /* port ID  (通信端口号)*/
     __u32       nl_groups;  /* multicast groups mask */
};

3. struct nlmsghdr 结构:

/* struct nlmsghd 是netlink消息头*/
struct nlmsghdr {   
    __u32       nlmsg_len;  /* Length of message including header */
    __u16       nlmsg_type; /* Message content */
    __u16       nlmsg_flags;    /* Additional flags */ 
    __u32       nlmsg_seq;  /* Sequence number */
    __u32       nlmsg_pid;  /* Sending process port ID */
};
  • ① Total Length (32bit) 协议头与payload的总长度(包含中间对齐和payload尾部对齐的空间)
    ② Message Type (16bit)。除了预定义的几个类型外,新协议可以自由的加入自己的消息类型。类型对netlink核心透明
    ③ Message Flags (16bit)。用于描述协议的行为,对netlink核心不透明
    ④ Sequence Number (32bit)。可选,用于标志已发送的消息,如错误消息可以引用一个已发送消息。
    ⑤ Port Number (32bit)。目的端口。若未指定,则会发送给内核,内核时此值为 0
  • nlmsg_type:消息状态,内核在include/uapi/linux/netlink.h中定义了以下4种通用的消息类型
  • nlmsg_type决定这次要执行的操作,如查询当前路由表信息,所使用的就是RTM_GETROUTE。根据采用的nlmsg_type不同,还要选取不同的数据结构来填充到nlmsghdr后面
  • status = recvmsg (sock, &msg, 0);
//nlmsg_type 类型枚举  
//标准nlmsg_type
#define NLMSG_NOOP      0x1 /* Nothing.     */
#define NLMSG_ERROR     0x2 /* Error        */
#define NLMSG_DONE      0x3 /* End of a dump    */
#define NLMSG_OVERRUN       0x4 /* Data lost        */
#define NLMSG_MIN_TYPE      0x10    /* < 0x10: reserved control messages */

//查询当前路由表信息等
RTM_NEWLINK ifinfomsg
RTM_DELLINK
RTM_GETLINK
RTM_NEWADDR ifaddrmsg
RTM_DELADDR
RTM_GETADDR
RTM_NEWROUTE rtmsg
RTM_DELROUTE
RTM_GETROUTE
RTM_NEWNEIGH ndmsg/nda_chcheinfo
RTM_DELNEIGH
RTM_GETNEIGH
RTM_NEWRULE rtmsg
RTM_DELRULE
RTM_GETRULE
RTM_NEWQDISC tcmsg
RTM_DELQDISC
RTM_GETQDISC
RTM_NEWTCLASS tcmsg
RTM_DELTCLASS
RTM_GETTCLASS
RTM_NEWTFILTER tcmsg
RTM_DELTFILTER
  • 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        */
#define NLM_F_DUMP_INTR     16  /* Dump was inconsistent due to sequence change */

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

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

4. struct msghdr 结构体

此结构又与sockaddr_nl,iovec和nlmsghdr三个结构相关,因此必须依次对这些数据结构进行初始化。目的端nl_pid必须为为0,表示接收方为内核

struct iovec {                    /* Scatter/gather array items */
     void  *iov_base;              /* Starting address */
     size_t iov_len;               /* Number of bytes to transfer */
 };
  /* iov_base: iov_base指向数据包缓冲区,即参数buff,iov_len是buff的长度。msghdr中允许一次传递多个buff,以数组的形式组织在 msg_iov中,msg_iovlen就记录数组的长度 (即有多少个buff)  */
 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 */
 };

如果我们需要返回一个 ACK 消息,可以对 flags 标志进行设置如下:

/* Request an acknowledgement by setting NLM_F_ACK */
  n->nlmsg_flags |= NLM_F_ACK;

5.netlink常用宏

#define NLMSG_ALIGNTO   4U
 /* 宏NLMSG_ALIGN(len)用于得到不小于len且字节对齐的最小数值 */
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
 /* Netlink 头部长度 */
#define NLMSG_HDRLEN     ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
/* 计算消息数据len的真实消息长度(消息体 + 消息头)*/
#define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)
/* 宏NLMSG_SPACE(len)返回不小于NLMSG_LENGTH(len)且字节对齐的最小数值 */
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
/* 宏NLMSG_DATA(nlh)用于取得消息的数据部分的首地址,设置和读取消息数据部分时需要使用该宏 */

#define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
/* 宏NLMSG_NEXT(nlh,len)用于得到下一个消息的首地址, 同时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))
/* NLMSG_PAYLOAD(nlh,len) 用于返回payload的长度*/
#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))

三 使用的例子

在应用层侧:

我们准要需要做一下的动作:

1.创建一个使用的AF_NETLINK簇的socket,并定义使用的哪个协议

2.本地端点的信息的填充以及绑定

3.目的端点的信息的填充

4.构造发送 的消息,也就是填充struct msghdr 结构体。里面包括目的端点信息,消息头,负载数据等。主要是几个关键的结构的变量填充

5.数据发送 ,需要使用netlink中的宏做一些处理

6.数据接收 ,需要使用netlink中定义的宏方便提取出数据

在linux 内核驱动侧:

1.通过netlink_kernel_create函数申请服务器端的套接字nl_sk,netlink协议类型NETLINK_TEST

2.设置一个nl_data_handler是一个钩子函数,收到消息就被回调对用户数据进行处理

3.使用NLMSG_DATA宏获取用户进程发送过来的数据。

4.重新申请一个套接字缓冲区

5.nlmsg_put 函数将填充 netlink 数据报头

6.netlink_unicast 发送数据包到用户进程

下面是附上的一个demo:

仅设计一个具有echo功能的协议。不在标准nlmsghdr之后的payload中定义自己的协议头,payload即为echo文本串,不使用netlink提供的消息标志。定义两种消息类型:NLMSG_GETECHO(用于echo请求包)和NLMSG_SETECHO(用于echo响应包。

应用层:

//应用层代码
#include <stdlib.h>  
#include <stdio.h>  
#include <unistd.h>  
#include <linux/netlink.h>  
#include <sys/socket.h>  
#include <strings.h>  
#include <string.h>  
#define NETLINK_TEST 31 // 自定义的协议号  
/** 消息类型 **/  
#define NLMSG_SETECHO 0x11  
#define NLMSG_GETECHO 0x12  
/** 最大协议负荷(固定) **/  
#define MAX_PAYLOAD 101  
  
struct sockaddr_nl src_addr, dst_addr;  
struct iovec iov;  
int sockfd;  
struct nlmsghdr *nlh = NULL;  
struct msghdr msg;  
  
int main( int argc, char **argv)  
{  
    if (argc != 2) {  
        printf("usage: ./a.out <str>\n");  
        exit(-1);  
    }  
  
    sockfd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_TEST); // 创建NETLINK_TEST协议的socket  
    /* 设置本地端点并绑定,用于侦听 */  
    bzero(&src_addr, sizeof(src_addr));  
    src_addr.nl_family = AF_NETLINK;  
    src_addr.nl_pid = getpid();  
    src_addr.nl_groups = 0; //未加入多播组  
    bind(sockfd, (struct sockaddr*)&src_addr, sizeof(src_addr));  
    /* 构造目的端点,用于发送 */  
    bzero(&dst_addr, sizeof(dst_addr));  
    dst_addr.nl_family = AF_NETLINK;  
    dst_addr.nl_pid = 0; // 表示内核  
    dst_addr.nl_groups = 0; //未指定接收多播组   
    /* 构造发送消息 */  
    nlh = (struct nlmsghdr *) malloc(NLMSG_SPACE(MAX_PAYLOAD));  
    nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD); //保证对齐  
    nlh->nlmsg_pid = getpid();  /* self pid */  
    nlh->nlmsg_flags = 0;  
    nlh->nlmsg_type = NLMSG_GETECHO;  
    strcpy((char *)NLMSG_DATA(nlh), argv[1]);  
    iov.iov_base = (void *)nlh;  
    iov.iov_len = nlh->nlmsg_len;  
    msg.msg_name = (void *)&dst_addr;  
    msg.msg_namelen = sizeof(dst_addr);  
    msg.msg_iov = &iov;  
    msg.msg_iovlen = 1;  
  
    sendmsg(sockfd, &msg, 0); // 发送  
    /* 接收消息并打印 */  
    memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));  
    recvmsg(sockfd, &msg, 0);  
    printf(" Received message payload: %s\n",  
           (char *) NLMSG_DATA(nlh));  
      printf(" Received message payload: %s %lx %lx\n",  
          (char *) NLMSG_DATA(nlh) ,msg.msg_iov->iov_len
          ,(unsigned long)((struct nlmsghdr *)msg.msg_iov->iov_base)->nlmsg_len); 
}

linux内核端:

//内核端代码,实现数据的收,然后加上"[from kernel]:"后原样返回给用户层
#include <linux/module.h>  
#include <linux/netlink.h>  
#include <net/netlink.h>  
#include <net/net_namespace.h>  
#define NETLINK_TEST 31  
  
#define NLMSG_SETECHO 0x11  
#define NLMSG_GETECHO 0x12  
  
static struct sock *sk; //内核端socket  
static void nl_custom_data_ready(struct sk_buff *skb); //接收消息回调函数  
  
int __init nl_custom_init(void)  
{  
    struct netlink_kernel_cfg nlcfg = {  
        .input = nl_custom_data_ready,  
    };  
    sk = netlink_kernel_create(&init_net, NETLINK_TEST, &nlcfg);  
    printk(KERN_INFO "initialed ok!\n");  
    if (!sk) {  
        printk(KERN_INFO "netlink create error!\n");  
    }  
    return 0;  
}  
void __exit nl_custom_exit(void)  
{  
    printk(KERN_INFO "existing...\n");  
    netlink_kernel_release(sk);  
}  
  
static void nl_custom_data_ready(struct sk_buff *skb)  
{  
    struct nlmsghdr *nlh;  
    void *payload;  
    struct sk_buff *out_skb;  
    void *out_payload;  
    struct nlmsghdr *out_nlh;  
    int payload_len; // with padding, but ok for echo   
    nlh = nlmsg_hdr(skb);  
    switch(nlh->nlmsg_type)  
    {  
        case NLMSG_SETECHO:  
            break;  
        case NLMSG_GETECHO:  
            payload = nlmsg_data(nlh);  
            payload_len = nlmsg_len(nlh);  
            printk(KERN_INFO "payload_len = %d\n", payload_len);  
            printk(KERN_INFO "Recievid: %s, From: %d\n", (char *)payload, nlh->nlmsg_pid);  
            out_skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); //分配足以存放默认大小的sk_buff  
            if (!out_skb) goto failure;  
            out_nlh = nlmsg_put(out_skb, 0, 0, NLMSG_SETECHO, payload_len, 0); //填充协议头数据  
            if (!out_nlh) goto failure;  
            out_payload = nlmsg_data(out_nlh);  
            strcpy(out_payload, "[from kernel]:"); // 在响应中加入字符串,以示区别  
            strcat(out_payload, payload);  
            nlmsg_unicast(sk, out_skb, nlh->nlmsg_pid);  
            break;  
        default:  
            printk(KERN_INFO "Unknow msgtype recieved!\n");  
    }  
    return;  
failure:  
    printk(KERN_INFO " failed in fun dataready!\n");  
}  
module_init(nl_custom_init);  
module_exit(nl_custom_exit);  
  
MODULE_LICENSE("GPL");  
MODULE_DESCRIPTION("a simple example for custom netlink protocal family");  
MODULE_AUTHOR("RSLjdkt"); 

四 内核中使用的实例

内核hotplug事件---利用Netlink处理hotplug实现热插拔监控_硬族科技的博客-CSDN博客_hotplug

其实在常用的路由器的cli 管理系统中,就有着这么一套通过netlink来管控路由的策略,以及实现的接口。可以去看看zebra源码。

在openwrt的那一套中,接口的管理换成了netifd去管理接口了。但实际添加路由之类的应该还是用的ip route 等这一类的工具,这些有的是使用的netlink,有的是使用ioctl进行控制的。

参考资料

linux下netlink的使用简介 - 专注it - 博客园

Linux netlink之添加一个简单协议_weixin_41666796的博客-CSDN博客

netlink实例_NeiborGirl的博客-CSDN博客

AF_NetLink结构体及例程_Nerazzur的博客-CSDN博客_af_netlink

Netlink机制详解_xinyuan0214的博客-CSDN博客_netlink_route

https://blog.csdn.net/luckyapple1028/article/details/50839395

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值