目录
[一、Connector数据结构和 API](#一、Connector数据结构和 API)
[1.1 消息头结构体](#1.1 消息头结构体)
[1.2 连接器id结构体](#1.2 连接器id结构体)
[1.3 netlink参数结构体](#1.3 netlink参数结构体)
简介:
连接器是一种用户态与内核态的通信方式,本质上,连接器是一种netlink,它的 netlink 协议号为 NETLINK_CONNECTOR,与一般的 netlink 相比,它提供了更容易的使用接口。目前,稳定内核有两个连接器应用实例,一个是进程事件连接器,另一个是 CIFS 文件系统。连接器实现代码在内核的 driver/connector 目录中。用户可以配置内核菜单选择是否启用连接器。
任何内核模块要想使用连接器,必须先注册一个标识 ID 和 回调函数,当连接器收到 netlink 消息后,会根据消息对应的标识 ID 调用相应该 ID 的回调函数。
连接器系统框架如图:

一、Connector数据结构和 API
1、数据结构体
1.1 消息头结构体
cpp
/* include/uapi/linux/netlink.h */
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 */
};
1.2 连接器id结构体
struct cb_id 是连接器实例的标识 ID,它用于确定 netlink 消息与回调函数的对应关系。当连接器接收到标识 ID 为 {idx,val} 的 netlink 消息时,注册的回调函数 void (*callback) (void *) 将被调用。该回调函数的参数为结构 struct cn_msg 的指针
cpp
/* include/uapi/linux/connector.h */
struct cb_id {
__u32 idx;
__u32 val;
};
struct cn_msg {
struct cb_id id;
__u32 seq;
__u32 ack;
__u16 len; /* Length of the following data */
__u16 flags;
__u8 data[0];
};
1.3 netlink参数结构体
cpp
/* 源码路径:include/linux/netlink.h */
struct netlink_skb_parms {
struct scm_creds creds; /* Skb credentials */
__u32 portid;
__u32 dst_group;
__u32 flags;
struct sock *sk;
bool nsid_is_set;
int nsid;
};
2、注册callback
接口函数 cn_add_callback 用于向连接器注册新的连接器实例以及相应的回调函数
cpp
int cn_add_callback(struct cb_id *id, const char *name,
void (*callback)(struct cn_msg *,
struct netlink_skb_parms *))
**功能:**向连接器注册新的连接器实例,当收到标识ID则callback函数被调用
参数:
- id :注册的标识 ID;
- name :连接器回调函数的符号名;
- callback :回调函数
3、卸载callback
接口函数 cn_del_callback 用于卸载回调函数
cpp
void cn_del_callback(struct cb_id *id)
**功能:**卸载callback函数
参数:
- id:卸载 cn_add_callback 时注册的 id;
4、发送消息
接口函数 cn_netlink_send 用于向指定的组发送消息(用于向用户态发送netlink消息),它可以在任何上下文安全地调用,但是,如果内存不足,可能会发送失败
cpp
int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __group, gfp_t gfp_mask)
**功能:**向给定的组发送消息(向用户态发送netlink消息)
参数:
- msg:netlink 消息的消息头;
- __group:接收消息的组。如果它为 0,那么连接器将搜索所有注册的连接器用户,最终将发送给用户 ID 与在 msg 中的 ID 相同的组,但如果 __group 不为 0,消息将发送给 __group 指定的组;
- gfp_mask:页分配标志;
二、参考示例

ncp 为 Netlink Connector Process,即用户态我们需要开发的程序
1、用户态使用连接器
实现了用户态发送/接收连接器msg
cpp
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <linux/netlink.h>
/* CN_NETLINK_USERS 定义在 include/uapi/linux/connector.h */
#define CN_VIF_ID CN_NETLINK_USERS + 3
#define CN_VIF_VAL 0x5555
#define RX_BUF_SIZE 100 //接收buf size
#define MAX_PLOAD 100 //发送message内存size
typedef struct
{
struct nlmsghdr hdr;
char msg[RX_BUF_SIZE];
}RX_KERNEL_MSG;
static int nl_connector_send(int _skfd, char *_pBuf, int _len)
{
static int seq = 0;
int ret = -1;
struct nlmsghdr *nlhdr;
struct cn_msg *cnmsg;
/* 配置消息头 */
nlhdr = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PLOAD)); //申请buf
//初始化头 nlmsghdr
memset(nlhdr, 0, sizeof(struct nlmsghdr));
nlhdr->nlmsg_len = NLMSG_SPACE(_len); //包括netlink消息头在内,整个消息长度
nlhdr->nlmsg_flags = 0; //消息标志
nlhdr->nlmsg_type = NLMSG_DONE; //消息类型
nlhdr->nlmsg_seq = 0; //消息报文的序列号
nlhdr->nlmsg_pid = getpid(); //消息中加入本应用端口pid
/* 设置数据 */
cnmsg = (struct cn_msg*)NLMSG_DATA(nlhdr); //找到存放 cn_msg 的地址
cnmsg->id.idx = CN_VIF_ID;
cnmsg->id.val = CN_VIF_VAL;
cnmsg->seq = seq++;
cnmsg->ack = 0;
memcpy(cnmsg->data, _pBuf, _len); //这里要注意越界风险
cnmsg->len = _len;
/* 发送数据 */
ret = send(_skfd, nlhdr, nlhdr->nlmsg_len, 0); //将message消息发送给内核
if(!ret){
perror("Failed to send:");
exit(-1);
}
free((void *)nlhdr);
return ret;
}
static int nl_connector_recv(int _skfd, char *_pBuf, int _len)
{
int ret = -1;
struct nlmsghdr *nlhdr_rx;
char *pData;
len = recv(_skfd, _pBuf, _len, 0);
if (len == -1) {
log_e("recv buf failed\n");
// close(ksif->fd);
return -1;
}
nlhdr_rx = (struct nlmsghdr *)_pBuf; //msg头部分
pData = (struct cn_msg *)NLMSG_DATA(nlhdr_rx); //数据部分
//判断msc头类型
switch (nlhdr_rx->nlmsg_type) {
case NLMSG_ERROR:
printf("Error message received.\n");
ret = 0;
break;
case NLMSG_DONE:
ret = len;
if(data->id.idx != CN_VIF_ID || data->id.val != CN_VIF_VAL)
return 0;
printf("[%x.%x] [%08u.%08u] %dbytes\n", data->id.idx, data->id.val, data->seq, data->ack, data->len);
break;
default:
break;
}
return ret;
}
int main(int argc, char* argv[])
{
char *data = "This message is from user's space";
char rx_buf[NL_SPACE(MAX_PLOAD)];
//初始化
struct sockaddr_nl l_local = {0}; //sockaddr_nl 是 netlink 使用的地址数据结构
int skfd = -1;
int seq = 0;
int ret = -1;
/* 1.创建NETLINK socket */
skfd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
if(skfd < 0){
printf("can not connect to kernel by netlink type %d\n", NETLINK_CONNECTOR);
return -1;
}
//初始化 netlink 地址
l_local.nl_family = AF_NETLINK;
l_local.nl_pid = getpid();
l_local.nl_groups = -1; //请求组的位掩码,-1转为无符号32位的0xffffffff(表示都接收)
if(bind(skfd, (struct sockaddr *)&l_local, sizeof(struct sockaddr_nl)) != 0){
printf("bind() error\n");
close(skfd);
return -1;
}
/* 2.发送数据 */
nl_connector_send(skfd, (char *)data, sizeof(data));
/* 3.接收内核发来的信息 */
nl_connector_recv(skfd, (char *)rx_buf, sizeof(rx_buf));
printf("User Receive ACK from kernel:%s\r\n",(char *)info.msg);
//内核和用户进行通信
close(skfd);
return 0;
}
2、内核态使用连接器
实现内核态接收/发送连接器msg
cpp
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <net/sock.h>
#include <linux/netlink.h>
#include <linux/connector.h>
#include <uapi/linux/connector.h>
/* 连接器的唯一标识,连接器需要它来找到对应的连接器实例 */
#define CN_VIF_ID CN_NETLINK_USERS + 3
#define CN_VIF_VAL 0x5555
static struct cb_id g_cb_id = { CN_VIF_ID, CN_VIF_VAL };
/* cn 发送 */
static int cn_func_send(const uint8_t *_pBuf, size_t _len)
{
struct cn_msg *m;
static int seq_cnt = 0;
m = kzalloc(sizeof(*m) + _len, GFP_ATOMIC);
if (!m) {
printk("kzalloc cn_msg failed\n");
return 0;
}
m->id.idx = CN_VIF_ID;
m->id.val = CN_VIF_VAL;
m->seq = seq_cnt++;
m->len = _len;
memcpy(m + 1, _pBuf, m->len);
//#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,15,0)
cn_netlink_send(m, 0, 0, GFP_ATOMIC);
//#else
// cn_netlink_send(m, 0, GFP_ATOMIC);
//#endif
kfree(m);
return _len;
}
/* 接收回调函数 */
static void cn_func_callback(struct cn_msg *cn_msg, struct netlink_skb_parms *nsp)
{
/* 打印接收到的数据和长度 */
printk("kernel space Rx: data: %s, len: %d\n", cn_msg->data, cn_msg->len);
/* 内核态应答数据 */
cn_func_send("kernel space Tx: ACK", sizeof("kernel space Tx: ACK"));
}
int __init netlink_connector_init(void)
{
int ret = -1;
/* 注册 cn 接收回调函数 */
ret = cn_add_callback(&g_cb_id, "cn_func", cn_func_callback);
if(ret) {
pr_err("cn_add_callback cn_func failed\n");
return ret;
}
printk(KERN_DEBUG "netlink_connector_init!!\n");
return ret;
}
void __exit netlink_connector_exit(void)
{
cn_del_callback(&g_cb_id);
printk(KERN_DEBUG "netlink_connector_exit!!\n");
}
module_init(netlink_connector_init);
module_exit(netlink_connector_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("dongao");
驱动加载后查看结果:
cpp
$ cat /proc/net/connector
Name ID
cn_func 14:21845