Linux Netlink通信机制详解

 

前面有一篇文章其实已经介绍过Netlink方面的知识,还有一个内核和用户空间之间的一个交互例子,这篇文章主要是更细节和基础的知识介绍!

Netlink 是一种特殊的 socket,它是 Linux 所特有的,由于传送的消息是暂存在socket接收缓存中,并不被接收者立即处理,所以netlink是一种异步通信机制。 系统调用和ioctl 则是同步通信机制。

用户空间进程可以通过标准socket API来实现消息的发送、接收,在Linux中,有很多用户空间和内核空间的交互都是通过Netlink机制完成的,在Linux3.0的内核版本中定义了下面的21个用于Netlink通信的宏,其中默认的最大值为32.我这里重点关注的是IPv6路由部分的通信过程。

  在include/linux/`netlink.h文件中定义了下面的用于通信的宏!

  1. #define NETLINK_ROUTE        0    /* Routing/device hook                */
  2. #define NETLINK_UNUSED        1    /* Unused number                */
  3. #define NETLINK_USERSOCK    2    /* Reserved for user mode socket protocols     */
  4. #define NETLINK_FIREWALL    3    /* Firewalling hook                */
  5. #define NETLINK_INET_DIAG    4    /* INET socket monitoring            */
  6. #define NETLINK_NFLOG        5    /* netfilter/iptables ULOG */
  7. #define NETLINK_XFRM        6    /* ipsec */
  8. #define NETLINK_SELINUX        7    /* SELinux event notifications */
  9. #define NETLINK_ISCSI        8    /* Open-iSCSI */
  10. #define NETLINK_AUDIT        9    /* auditing */
  11. #define NETLINK_FIB_LOOKUP    10    
  12. #define NETLINK_CONNECTOR    11
  13. #define NETLINK_NETFILTER    12    /* netfilter subsystem */
  14. #define NETLINK_IP6_FW        13
  15. #define NETLINK_DNRTMSG        14    /* DECnet routing messages */
  16. #define NETLINK_KOBJECT_UEVENT    15    /* Kernel messages to userspace */
  17. #define NETLINK_GENERIC        16
  18. /* leave room for NETLINK_DM (DM Events) */
  19. #define NETLINK_SCSITRANSPORT    18    /* SCSI Transports */
  20. #define NETLINK_ECRYPTFS    19
  21. #define NETLINK_RDMA        20
  22.  
  23. #define MAX_LINKS 32

   用户态可以使用标准的socket APIs, socket(), bind(), sendmsg(), recvmsg() 和 close() 等函数就能很容易地使用 netlink socket,我们在用户空间可以直接通过socket函数来使用Netlink通信,例如可以通过下面的方式:

  1. sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);

说明:第一个参数必须是 AF_NETLINK 或 PF_NETLINK,在 Linux 中,它们俩实际为一个东西,它表示要使用netlink,第二个参数必须是SOCK_RAW或SOCK_DGRAM, 第三个参数指定netlink协议类型,可以是自己在netlink.h中定义的,也可以是内核中已经定义好的。上面的例子使用主要是路由的Netlink协议。也可以是上面21中协议类型的其中之一。

NETLINK_GENERIC是一个通用的协议类型,它是专门为用户使用的,因此,用户可以直接使用它,而不必再添加新的协议类型。

对于每一个netlink协议类型,可以使用多播的概念,最多可以有 32个多播组,每一个多播组用一个位表示,netlink 的多播特性使得发送消息给同一个组仅需要一次系统调用,因而对于需要多播消息的应用而言,大大地降低了系统调用的次数。

下面介绍一下主要的数据结构:

  1. struct sockaddr_nl {
  2.     sa_family_t    nl_family;    /* AF_NETLINK    */
  3.     unsigned short    nl_pad;        /* zero        */
  4.     __u32        nl_pid;        /* port ID    */
  5.    __u32        nl_groups;    /* multicast groups mask */
  6. };

说明:(1)        sa_family_t      nl_family;         

      一般为AF_NETLINK,

(2)       unsigned short         nl_pad;

 字段 nl_pad 当前没有使用,因此要总是设置为 0,

(3)__u32              nl_pid;              

 绑定时用于指定绑定者的进程号,发送消息时用于指定接收进程号,如果希望内核处理多播消息,就把该字段设置为 0,否则设置为处理消息的进程 ID。传递给 bind 函数的地址的 nl_pid 字段应当设置为本进程的进程 ID,这相当于 netlink socket 的本地地址。但是,对于一个netlink socket 的情况,字段nl_pid 则可以设置为其它的值,如:

pthread_self() << 16 | getpid();

因此字段 nl_pid 实际上未必是进程 ID,它只是用于区分不同的接收者或发送者的一个标识,用户可以根据自己需要设置该字段。

(4)   __u32                 nl_groups;

    绑定时用于指定绑定者所要加入的多播组,这样绑定者就可以接收多播消息,发送 消息时可以用于指定多播组,这样就可以将消息发给多个接收者。这里nl_groups 为32位的无符号整形,所以可以指定32个多播组,每个进程可以加入多个多播组, 因为多播组是通过“或”操作,如果设置为 0,表示调用者不加入任何多播组。这里就是Netlink多播的概念!和通信中的多播概念有点类似。

 

 

 

Netlink是一种内核层与应用层通信的一种机制,比如说在做一个内核模块的时候,往往会需要应用层提供一些配置信息,这时候就可以使用netlink。netlink包括内核层和应用层,内核层注册一个netlink协议,然后定义一些消息类型,之后就可以等待来自应用层的消息了。这篇博文给出一个简单的netlink内核模块使用例子,应用层会在接下来的博文中给出。

内核版本:3.4.39。 不同内核版本可能带来编译问题,可以升级内核来解决。升级3.4.39内核可以参考这一篇博客(https://blog.csdn.net/fuyuande/article/details/79429441)

netlink 内核端代码如下:nlkernel.c


 

/*
 *  Description : 内核netlink编程
 *  Date        :20180528
 *  Author      :mason
 *  Mail        : mrsonko@126.com
 *
 */
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/netlink.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <net/ip.h>
#include "nlkernel.h"
 
static int number;
static struct sock *nlsock = NULL;  // netlink 套接字
 
// netlink消息回复,用于向应用层传递数据
static void netlink_sendto_userapp(unsigned int dst_pid) {
    struct sk_buff *skb = NULL;
    struct nlmsghdr *nlh = NULL;
    int datalen;
    int *pnum;
    datalen = NLMSG_SPACE(sizeof(int));
 
    skb = alloc_skb(datalen, GFP_KERNEL);
    if(!skb)
    {
        log("alloc skb error.\r\n");
        return ;
    }
    // 数据初始化
    nlh = nlmsg_put(skb, 0, 0, 0, sizeof(int), 0);
    nlh->nlmsg_pid = 0;
    nlh->nlmsg_type = NLKERNEL_GET;
    
    pnum = (int *)NLMSG_DATA(nlh);
    *pnum = number;    
    netlink_unicast(nlsock, skb, dst_pid, MSG_DONTWAIT);
    log("netlink send done \r\n");
    
    return;    
}
 
/* netlink消息处理函数 */
static int netlink_kernel_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
{
    int *value = NULL;
    switch(nlh->nlmsg_type){
        // 设置
        case NLKERNEL_SET :
            log("kernel receive netlink set msg!\r\n");
            value = (int *)NLMSG_DATA(nlh);            
            number = *value;
            break;
        // 获取
        case NLKERNEL_GET :
            log("kernel receive netlink get msg!\r\n");
            netlink_sendto_userapp(nlh->nlmsg_pid);
            break;
        default:
            log("unrecognized netlink message type : %u \r\n",nlh->nlmsg_type);
            break;
    }   
    return 0;
}   
 
 
 
 
static void netlink_kernel_rcv(struct sk_buff *skb)
{
    int res;
    res = netlink_rcv_skb(skb, &netlink_kernel_rcv_msg);
    return;
}
 
 
 
 
// 模块入口函数
static int __init nlkernel_init(void) {
    log("nlkernel init \r\n");
    // 注册netlink协议
    nlsock = netlink_kernel_create(&init_net, NETLINK_TEST_MODULE, 0, netlink_kernel_rcv, NULL, THIS_MODULE);
    if (!nlsock) {
        log("netlink module init fail \r\n");
        return -1;
    }
    return 0;
}
 
 
// 模块退出函数
static void __exit nlkernel_exit(void) {
    // 注销netlink协议
    if(nlsock)
    {
        netlink_kernel_release(nlsock);
        nlsock = NULL;
    }
    log("nlkernel exit \r\n");
    return ;
}
 
module_init(nlkernel_init)
module_exit(nlkernel_exit)
MODULE_AUTHOR("mason");
MODULE_DESCRIPTION("netlink kernel test");
MODULE_LICENSE("GPL");
 
 

nlkernel.h

#ifndef __NLKERNEL_H__
#define __NLKERNEL_H__
#define log(fmt, arg...)  printk(KERN_INFO"[bfd] %s:%d "fmt, __FUNCTION__, __LINE__, ##arg)
 
#ifndef NIPQUAD
#define NIPQUAD(addr) \
    ((unsigned char *)&addr)[0], \
    ((unsigned char *)&addr)[1], \
    ((unsigned char *)&addr)[2], \
    ((unsigned char *)&addr)[3]
#endif
 
#define NETLINK_TEST_MODULE         17      /* 定义 netlink 协议, */
 
typedef enum netlink_msg_type {             /* 定义 netlink 消息类型 */
    NLKERNEL_GET = NLMSG_MIN_TYPE +1,        /* value : 17 */
    NLKERNEL_SET,                            /* value : 18 */
 
    NLKERNEL_END,
}NETLINK_MSG_TYPE;
 
#endif

 

Makefile:

obj-m := nlkernel.o
 
PWD := $(shell pwd)
KERNEL_DIR := "/usr/src/linux-headers-"$(shell uname -r)/
 
modules:
    @$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules
clean:
    @rm -rf *.ko *.o *.mod.c *symvers *order *cmd
 

 

代码托管在github 上:

git@github.com:FuYuanDe/nlnetlink.git

 

编译完成后加载模块查看效果:

insmod nlkernel.ko

dmesg

 

 

 

linux下检测网卡与网线连通状态

 

Linux下检测网卡与网线连接状态,使用ioctl向socket发送SIOCETHTOOL命令字。

link_stat.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <linux/if.h>
#include <linux/sockios.h>
#include <linux/ethtool.h>

int get_netlink_status(const char *if_name);

int main()
{
    if(getuid() != 0)
    {
        fprintf(stderr, "Netlink Status Check Need Root Power.\n");
        return 1;
    }
    
    printf("Net link status: %d\n", get_netlink_status("eth0"));

    return 0;
}

// if_name like "ath0", "eth0". Notice: call this function
// need root privilege.
// return value:
// -1 -- error , details can check errno
// 1 -- interface link up
// 0 -- interface link down.
int get_netlink_status(const char *if_name)
{
    int skfd;
    struct ifreq ifr;
    struct ethtool_value edata;

    edata.cmd = ETHTOOL_GLINK;
    edata.data = 0;

    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, if_name, sizeof(ifr.ifr_name) - 1);
    ifr.ifr_data = (char *) &edata;

    if (( skfd = socket( AF_INET, SOCK_DGRAM, 0 )) < 0)
        return -1;

    if(ioctl( skfd, SIOCETHTOOL, &ifr ) == -1)
    {
        close(skfd);
        return -1;
    }

    close(skfd);
    return edata.data;
}

 

 

之前有一篇文章《Netlink实现Linux内核与用户空间通信》专门介绍了Netlink相比其他内核交互方式的优点以及Netlink的调用方法,并以NETLINK_KOBJECT_UEVENT(内核事件向用户态通知)为例演示了U盘热插拔信息的捕捉,衍生出另一篇文章《Linux下自动检测USB热插拔》,今天尝试用Netlink来捕捉一下网络接口信息,实现的主要功能是实时打印发生变化的网络接口的序列号、上下线状态和接口名称。    

为了创建一个 netlink socket,用户需要使用如下参数调用 socket():

 

  1. #include <sys/types.h>  
  2. #include <sys/socket.h>  
  3. #include <asm/types.h>  
  4. #include <linux/netlink.h>  
  5. #include <linux/rtnetlink.h>  
  6. #include <stdlib.h>  
  7. #include <stdio.h>  
  8. #include <sys/ioctl.h>  
  9. #include <linux/if.h>  
  10. #include <string.h>  
  11.   
  12. #define BUFLEN 20480  
  13.   
  14. int main(int argc, char *argv[])  
  15. {  
  16.     int fd, retval;  
  17.     char buf[BUFLEN] = {0};  
  18.     int len = BUFLEN;  
  19.     struct sockaddr_nl addr;  
  20.     struct nlmsghdr *nh;  
  21.     struct ifinfomsg *ifinfo;  
  22.     struct rtattr *attr;  
  23.   
  24.     fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);  
  25.     setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &len, sizeof(len));  
  26.     memset(&addr, 0, sizeof(addr));  
  27.     addr.nl_family = AF_NETLINK;  
  28.     addr.nl_groups = RTNLGRP_LINK;  
  29.     bind(fd, (struct sockaddr*)&addr, sizeof(addr));  
  30.     while ((retval = read(fd, buf, BUFLEN)) > 0)  
  31.     {  
  32.         for (nh = (struct nlmsghdr *)buf; NLMSG_OK(nh, retval); nh = NLMSG_NEXT(nh, retval))  
  33.         {  
  34.             if (nh->nlmsg_type == NLMSG_DONE)  
  35.                 break;  
  36.             else if (nh->nlmsg_type == NLMSG_ERROR)  
  37.                 return;  
  38.             else if (nh->nlmsg_type != RTM_NEWLINK)  
  39.                 continue;  
  40.             ifinfo = NLMSG_DATA(nh);  
  41.             printf("%u: %s", ifinfo->ifi_index,  
  42.                     (ifinfo->ifi_flags & IFF_LOWER_UP) ? "up" : "down" );  
  43.             attr = (struct rtattr*)(((char*)nh) + NLMSG_SPACE(sizeof(*ifinfo)));  
  44.             len = nh->nlmsg_len - NLMSG_SPACE(sizeof(*ifinfo));  
  45.             for (; RTA_OK(attr, len); attr = RTA_NEXT(attr, len))  
  46.             {  
  47.                 if (attr->rta_type == IFLA_IFNAME)  
  48.                 {  
  49.                     printf(" %s", (char*)RTA_DATA(attr));  
  50.                     break;  
  51.                 }  
  52.             }  
  53.             printf("\n");  
  54.         }  
  55.     }  
  56.   
  57.     return 0;  
  58. }  

 

 

 

 

 

https://github.com/FuYuanDe/nlnetlink

 

转载:

https://blog.csdn.net/fuyuande/article/details/80489988

https://blog.csdn.net/zcabcd123/article/details/8272423

https://blog.csdn.net/sourthstar/article/details/7975999

http://blog.chinaunix.net/uid-20357359-id-1963608.html?page=2

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值