Linux内核netlink机制 - 连接器(Netlink Connector)

打印 上一主题 下一主题

主题 1973|帖子 1973|积分 5919

目录
简介:

一、Connector数据结构和 API
1、数据结构体
1.1 消息头结构体
1.2 连接器id结构体
1.3 netlink参数结构体
2、注册callback
3、卸载callback
4、发送消息
二、参考示例
1、用户态使用连接器
2、内核态使用连接器


简介:


        连接器是一种用户态与内核态的通讯方式,本质上,连接器是一种netlink,它的 netlink 协议号为 NETLINK_CONNECTOR,与一般的 netlink 相比,它提供了更容易的使用接口。现在,稳定内核有两个连接器应用实例,一个是历程变乱连接器,另一个是 CIFS 文件体系。连接器实今世码在内核的 driver/connector 目录中。用户可以配置内核菜单选择是否启用连接器。
        任何内核模块要想使用连接器,必须先注册一个标识 ID 和 回调函数,当连接器收到 netlink 消息后,会根据消息对应的标识 ID 调用相应该 ID 的回调函数。

连接器体系框架如图:



一、Connector数据结构和 API


1、数据结构体


1.1 消息头结构体

  1. /* include/uapi/linux/netlink.h */
  2. struct nlmsghdr {
  3.         __u32                nlmsg_len;        /* Length of message including header */
  4.         __u16                nlmsg_type;        /* Message content */
  5.         __u16                nlmsg_flags;        /* Additional flags */
  6.         __u32                nlmsg_seq;        /* Sequence number */
  7.         __u32                nlmsg_pid;        /* Sending process port ID */
  8. };
复制代码
       
1.2 连接器id结构体

        struct cb_id 是连接器实例的标识 ID,它用于确定 netlink 消息与回调函数的对应关系。当连接器接收到标识 ID 为 {idx,val} 的 netlink 消息时,注册的回调函数 void (*callback) (void *) 将被调用。该回调函数的参数为结构 struct cn_msg 的指针
  1. /* include/uapi/linux/connector.h */
  2. struct cb_id {
  3.         __u32 idx;
  4.         __u32 val;
  5. };
  6. struct cn_msg {
  7.         struct cb_id id;
  8.         __u32 seq;
  9.         __u32 ack;
  10.         __u16 len;                /* Length of the following data */
  11.         __u16 flags;
  12.         __u8 data[0];
  13. };
复制代码

1.3 netlink参数结构体

  1. /* 源码路径:include/linux/netlink.h */
  2. struct netlink_skb_parms {
  3.         struct scm_creds        creds;                /* Skb credentials        */
  4.         __u32                        portid;
  5.         __u32                        dst_group;
  6.         __u32                        flags;
  7.         struct sock                *sk;
  8.         bool                        nsid_is_set;
  9.         int                        nsid;
  10. };
复制代码

2、注册callback


接口函数 cn_add_callback 用于向连接器注册新的连接器实例以及相应的回调函数
  1. int cn_add_callback(struct cb_id *id, const char *name,
  2.                     void (*callback)(struct cn_msg *,
  3.                                      struct netlink_skb_parms *))
复制代码
功能:向连接器注册新的连接器实例,当收到标识ID则callback函数被调用
参数:


  • id :注册的标识 ID;
  • name :连接器回调函数的符号名;
  • callback :回调函数

3、卸载callback


接口函数 cn_del_callback 用于卸载回调函数
  1. void cn_del_callback(struct cb_id *id)
复制代码
功能:卸载callback函数
参数:


  • id:卸载 cn_add_callback 时注册的 id;

4、发送消息


接口函数 cn_netlink_send 用于向指定的组发送消息(用于向用户态发送netlink消息),它可以在任何上下文安全地调用,但是,如果内存不足,大概会发送失败
  1. 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
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/socket.h>
  4. #include <string.h>
  5. #include <linux/netlink.h>
  6. /* CN_NETLINK_USERS 定义在 include/uapi/linux/connector.h */
  7. #define CN_VIF_ID CN_NETLINK_USERS + 3
  8. #define CN_VIF_VAL 0x5555
  9. #define RX_BUF_SIZE                100                //接收buf size
  10. #define MAX_PLOAD                100                //发送message内存size
  11. typedef struct
  12. {
  13.     struct nlmsghdr hdr;
  14.     char msg[RX_BUF_SIZE];
  15. }RX_KERNEL_MSG;
  16. static int nl_connector_send(int _skfd, char *_pBuf, int _len)
  17. {
  18.     static int seq = 0;
  19.     int ret = -1;
  20.     struct nlmsghdr *nlhdr;
  21.     struct cn_msg *cnmsg;
  22. /* 配置消息头 */
  23.     nlhdr = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PLOAD));        //申请buf
  24.    
  25.     //初始化头 nlmsghdr
  26.     memset(nlhdr, 0, sizeof(struct nlmsghdr));
  27.     nlhdr->nlmsg_len = NLMSG_SPACE(_len); //包括netlink消息头在内,整个消息长度
  28.     nlhdr->nlmsg_flags = 0;            //消息标志
  29.     nlhdr->nlmsg_type = NLMSG_DONE;    //消息类型
  30.     nlhdr->nlmsg_seq = 0;              //消息报文的序列号
  31.     nlhdr->nlmsg_pid = getpid();                //消息中加入本应用端口pid
  32. /* 设置数据 */
  33.     cnmsg = (struct cn_msg*)NLMSG_DATA(nlhdr);        //找到存放 cn_msg 的地址
  34.     cnmsg->id.idx = CN_VIF_ID;
  35.         cnmsg->id.val = CN_VIF_VAL;
  36.         cnmsg->seq = seq++;
  37.         cnmsg->ack = 0;
  38.         memcpy(cnmsg->data, _pBuf, _len);        //这里要注意越界风险
  39.         cnmsg->len = _len;
  40. /* 发送数据 */
  41.     ret = send(_skfd, nlhdr, nlhdr->nlmsg_len, 0);        //将message消息发送给内核
  42.     if(!ret){
  43.         perror("Failed to send:");
  44.         exit(-1);
  45.     }
  46.     free((void *)nlhdr);
  47.     return ret;
  48. }
  49. static int nl_connector_recv(int _skfd, char *_pBuf, int _len)
  50. {
  51.     int ret = -1;
  52.     struct nlmsghdr *nlhdr_rx;
  53.     char *pData;
  54.     len = recv(_skfd, _pBuf, _len, 0);
  55.     if (len == -1) {
  56.                 log_e("recv buf failed\n");
  57.                 // close(ksif->fd);
  58.                 return -1;
  59.         }
  60.         nlhdr_rx = (struct nlmsghdr *)_pBuf;                                //msg头部分
  61.     pData = (struct cn_msg *)NLMSG_DATA(nlhdr_rx);                //数据部分
  62.     //判断msc头类型
  63.         switch (nlhdr_rx->nlmsg_type) {
  64.             case NLMSG_ERROR:
  65.                     printf("Error message received.\n");
  66.                     ret = 0;
  67.                     break;
  68.             case NLMSG_DONE:
  69.                     ret = len;
  70.                     if(data->id.idx != CN_VIF_ID || data->id.val != CN_VIF_VAL)
  71.                             return 0;
  72.                     printf("[%x.%x] [%08u.%08u] %dbytes\n", data->id.idx, data->id.val, data->seq, data->ack, data->len);
  73.                     break;
  74.             default:
  75.                     break;
  76.         }
  77.     return ret;
  78. }
  79. int main(int argc, char* argv[])
  80. {
  81.     char *data = "This message is from user's space";
  82.     char rx_buf[NL_SPACE(MAX_PLOAD)];
  83.    
  84.     //初始化
  85.     struct sockaddr_nl l_local = {0};    //sockaddr_nl 是 netlink 使用的地址数据结构
  86.     int skfd = -1;
  87.     int seq = 0;
  88.     int ret = -1;
  89. /* 1.创建NETLINK socket */
  90.     skfd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
  91.     if(skfd < 0){
  92.         printf("can not connect to kernel by netlink type %d\n", NETLINK_CONNECTOR);
  93.         return -1;
  94.     }
  95.     //初始化 netlink 地址
  96.     l_local.nl_family = AF_NETLINK;
  97.     l_local.nl_pid = getpid();
  98.     l_local.nl_groups = -1;        //请求组的位掩码,-1转为无符号32位的0xffffffff(表示都接收)
  99.     if(bind(skfd, (struct sockaddr *)&l_local, sizeof(struct sockaddr_nl)) != 0){
  100.         printf("bind() error\n");
  101.         close(skfd);
  102.         return -1;
  103.     }
  104. /* 2.发送数据 */
  105.     nl_connector_send(skfd, (char *)data, sizeof(data));
  106. /* 3.接收内核发来的信息 */
  107.     nl_connector_recv(skfd, (char *)rx_buf, sizeof(rx_buf));
  108.     printf("User Receive ACK from kernel:%s\r\n",(char *)info.msg);
  109.     //内核和用户进行通信
  110.     close(skfd);
  111.     return 0;
  112. }
复制代码

2、内核态使用连接器

实现内核态接收/发送连接器msg
  1. #include <linux/kernel.h>
  2. #include <linux/module.h>
  3. #include <linux/types.h>
  4. #include <linux/sched.h>
  5. #include <net/sock.h>
  6. #include <linux/netlink.h>
  7. #include <linux/connector.h>
  8. #include <uapi/linux/connector.h>
  9. /* 连接器的唯一标识,连接器需要它来找到对应的连接器实例 */
  10. #define CN_VIF_ID CN_NETLINK_USERS + 3
  11. #define CN_VIF_VAL 0x5555
  12. static struct cb_id g_cb_id = { CN_VIF_ID, CN_VIF_VAL };
  13. /* cn 发送 */
  14. static int cn_func_send(const uint8_t *_pBuf, size_t _len)
  15. {
  16.         struct cn_msg *m;
  17.     static int seq_cnt = 0;
  18.     m = kzalloc(sizeof(*m) + _len, GFP_ATOMIC);
  19.         if (!m) {
  20.                 printk("kzalloc cn_msg failed\n");
  21.                 return 0;
  22.         }
  23.     m->id.idx = CN_VIF_ID;
  24.     m->id.val = CN_VIF_VAL;
  25.         m->seq = seq_cnt++;
  26.         m->len = _len;
  27.         memcpy(m + 1, _pBuf, m->len);
  28.    
  29. //#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,15,0)
  30.         cn_netlink_send(m, 0, 0, GFP_ATOMIC);
  31. //#else
  32. //        cn_netlink_send(m, 0, GFP_ATOMIC);
  33. //#endif
  34.         kfree(m);
  35.         return _len;
  36. }
  37. /* 接收回调函数 */
  38. static void cn_func_callback(struct cn_msg *cn_msg, struct netlink_skb_parms *nsp)
  39. {
  40.     /* 打印接收到的数据和长度 */
  41.         printk("kernel space Rx: data: %s, len: %d\n", cn_msg->data, cn_msg->len);
  42.     /* 内核态应答数据 */
  43.     cn_func_send("kernel space Tx: ACK", sizeof("kernel space Tx: ACK"));
  44. }
  45. int __init netlink_connector_init(void)
  46. {
  47.     int ret = -1;
  48.     /* 注册 cn 接收回调函数 */
  49.         ret = cn_add_callback(&g_cb_id, "cn_func", cn_func_callback);
  50.         if(ret) {
  51.                 pr_err("cn_add_callback cn_func failed\n");
  52.                 return ret;
  53.         }
  54.     printk(KERN_DEBUG "netlink_connector_init!!\n");
  55.         return ret;
  56. }
  57. void __exit netlink_connector_exit(void)
  58. {
  59.     cn_del_callback(&g_cb_id);
  60.     printk(KERN_DEBUG "netlink_connector_exit!!\n");
  61. }
  62. module_init(netlink_connector_init);
  63. module_exit(netlink_connector_exit);
  64. MODULE_LICENSE("GPL");
  65. MODULE_AUTHOR("dongao");
复制代码
驱动加载后查看结果:
  1. $ cat /proc/net/connector
  2. Name            ID
  3. cn_func         14:21845
复制代码



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

三尺非寒

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表