linux之netlink 内核源码分析

一、netlink 出现的原因

Netlink 是一种IPC(Inter Process Commumicate)机制,它是一种用于内核与用户空间通信的机制,同时也可以用于进程间通信,但是主要原因是因为内核态需要与用户态异步通信。

进程间通信使用其他IPC机制。

在一般情况下,用户态和内核态通信会使用传统的Ioctl、sysfs属性文件或者procfs属性文件,这3种通信方式都是同步通信方式,由用户态主动发起向内核态的通信,内核无法主动发起通信。

Netlink是一种异步全双工的通信方式,它支持由内核态主动发起通信,内核为Netlink通信提供了一组特殊的API接口,用户态则基于socket API,内核发送的数据会保存在接收进程socket 的接收缓存中,由接收进程处理。

1.1 netlink 的优点

1、双向全双工异步传输,支持由内核主动发起传输通信,而不需要用户空间出发(例如使用ioctl这类的单工方式)。如此用户空间在等待内核某种触发条件满足时就无需不断轮询,而异步接收内核消息即可。

2、支持组播传输,即内核态可以将消息发送给多个接收进程,这样就不用每个进程单独来查询了。

二、netlink 内核源码的实现

2.1 netlink 子系统的初始化

内核源码位置:

af_netlink.c  提供netlink内核套接字的API 和netlink 协议的主要源码;

genetlink.c 提供了新的通用netlink API;

diag.c  监视接口模块,提供的API 用于读写有关的netlink套接字信息。

下面看一下af_netlink.c 的初始化接口:

//kernel/net/netlink/af_netlink.c

static int __init netlink_proto_init(void)
{
        int i;
        int err = proto_register(&netlink_proto, 0); // 向内核注册netlink_proto

        if (err != 0)
                goto out;

#if defined(CONFIG_BPF_SYSCALL) && defined(CONFIG_PROC_FS)
        err = bpf_iter_register();
        if (err)
                goto out;
#endif

        BUILD_BUG_ON(sizeof(struct netlink_skb_parms) > sizeof_field(struct sk_buff, cb));
        //创建并初始化nl_table 数组
        nl_table = kcalloc(MAX_LINKS, sizeof(*nl_table), GFP_KERNEL);
        if (!nl_table)
                goto panic;
        // 初始化nl_table
        for (i = 0; i < MAX_LINKS; i++) {
                if (rhashtable_init(&nl_table[i].hash,
                                    &netlink_rhashtable_params) < 0) {
                        while (--i > 0)
                                rhashtable_destroy(&nl_table[i].hash);
                        kfree(nl_table);
                        goto panic;
                }
        }
        初始化应用层使用的NETLINK_USERSOCK协议类型的netlink
        //用于应用层进程间通信---单独初始化,维持用户态进程间通信
        netlink_add_usersock_entry();

        sock_register(&netlink_family_ops); // 注册netlink 协议族的操作函数集
        register_pernet_subsys(&netlink_net_ops);//向网络空间注册操作集
        register_pernet_subsys(&netlink_tap_net_ops);
        /* The netlink device handler may be needed early. */
        rtnetlink_init();
out:
        return err;
panic:
        panic("netlink_init: Cannot allocate nl_table\n");
}

core_initcall(netlink_proto_init);

1、nl_table = kcalloc(MAX_LINKS, sizeof(*nl_table), GFP_KERNEL);

nl_table 表,是实现netlink 每种协议类型的数组,后续内核创建的不同协议类型的netlink 都将保存在这个表中,由该表统一维护。

下面展示 目前内核中的nl_table 有哪些类型:表项最多是32,目前没有全部使用


#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_SMC             22      /* SMC monitoring */

#define NETLINK_INET_DIAG       NETLINK_SOCK_DIAG

#define MAX_LINKS 32

内核中已经定义了22 中协议类型,其中NETLINK_ROUTE是用于设置和查询路由表等网络核心模块的。NETLINK_KOBJECT_UEVENT 是用于uevent 消息通信的,用于上层udev 事件,设备热插拔的通信机制。

对于在实际的项目中,可能会有一些定制化的需求,以上这几种专用的协议类型无法满足,这时可以在不超过最大32种类型的基础之上自行添加。但是一般情况下这样做有些不妥,于是内核开发者就设计了一种通用netlink 协议类型(Generic Netlink)NETLINK_GENERIC,它就是一个Netlink复用器,便于用户自行扩展子协议类型(后面我会使用该Generic Netlink 编写一个示例程序用于演示内核和用户空间的通信)。

netlink_table :

// kernel/net/netlink/af_netlink.h

struct netlink_table {
         hash(哈希表)用来索引同种协议类型的不同netlink套接字实例
        struct rhashtable       hash;
        struct hlist_head       mc_list;//为多播使用的sock散列表
        struct listeners __rcu  *listeners; //监听者掩码
        unsigned int            flags;
        unsigned int            groups;   //协议支持的最大多播组数量
        struct mutex            *cb_mutex;
        struct module           *module;
/*定义了一些函数指针,它们会在内核首次创建netlink时被赋值,后续应用层创建和绑定socket时调用到*/
        int                     (*bind)(struct net *net, int group);
        void                    (*unbind)(struct net *net, int group);
        bool                    (*compare)(struct net *net, struct sock *sock);
        int                     registered;
};

2、sock_register(&netlink_family_ops);

调用sock_register向内核注册协议族处理函数,即将netlink的socket创建处理函数注册到内核中,如此以后应用层创建netlink类型的socket时将会调用该协议族处理函数,其中netlink_family_ops函数的定义如下:

static const struct net_proto_family netlink_family_ops = {
        .family = PF_NETLINK,  //协议族的名字和AF_NETLINK
        .create = netlink_create,
        .owner  = THIS_MODULE,  /* for consistency 8) */
};

3、register_pernet_subsys(&netlink_net_ops);

调用register_pernet_subsys向内核所有的网络命名空间注册”子系统“的初始化和去初始化函数,这里的"子系统”并非指的是netlink子系统,而是一种通用的处理方式,在网络命名空间创建和注销时会调用这里注册的初始化和去初始化函数,当然对于已经存在的网络命名空间,在注册的过程中也会调用其初始化函数。netlink_net_ops定义如下:

static struct pernet_operations __net_initdata netlink_net_ops = {
        .init = netlink_net_init,
        .exit = netlink_net_exit,
};

其中netlink_net_init()会在文件系统中为每个网络命名空间创建一个proc入口,而netlink_net_exit()不用时销毁。

 4、rtnetlink_init();

调用rtnetlink_init()创建NETLINK_ROUTE协议类型的netlink,该种类型的netlink才是当初内核设计netlink的初衷,它用来传递网络路由子系统、邻居子系统、接口设置、防火墙等消息。至此整个netlink子系统初始化完成。

2.2 看一下netlink 使用的几个重要的数据结构

1、

/* optional Netlink kernel configuration parameters */
struct netlink_kernel_cfg {
        unsigned int    groups;
        unsigned int    flags;
        void            (*input)(struct sk_buff *skb);
        struct mutex    *cb_mutex;
        int             (*bind)(struct net *net, int group);
        void            (*unbind)(struct net *net, int group);
        bool            (*compare)(struct net *net, struct sock *sk);
};

该结构包含了内核netlink的可选参数:

groups  :  其中groups用于指定最大的多播组;

flags : flags成员可以为NL_CFG_F_NONROOT_RECV或NL_CFG_F_NONROOT_SEND,这两个符号前者用来限定非超级用户是否可以绑定到多播组,后者用来限定非超级用户是否可以发送组播;

input: input指针用于指定回调函数,该回调函数用于接收和处理来自用户空间的消息(若无需接收来自用户空间的消息可不指定);

最后的三个函数指针实现sock的绑定和解绑定等操作,会添加到nl_table对应的项中去。

2、netlink 属性头: struct nlattr

// kernel/include/uapi/linux/netlink.h
/*
 *  <------- NLA_HDRLEN ------> <-- NLA_ALIGN(payload)-->
 * +---------------------+- - -+- - - - - - - - - -+- - -+
 * |        Header       | Pad |     Payload       | Pad |
 * |   (struct nlattr)   | ing |                   | ing |
 * +---------------------+- - -+- - - - - - - - - -+- - -+
 *  <-------------- nlattr->nla_len -------------->
 */

struct nlattr {
        __u16           nla_len;
        __u16           nla_type;
};

/*
 * nla_type (16 bits)
 * +---+---+-------------------------------+
 * | N | O | Attribute Type                |
 * +---+---+-------------------------------+
 * N := Carries nested attributes
 * O := Payload stored in network byte order
 *
 * Note: The N and O flag are mutually exclusive.
 */

netlink的消息头后面跟着的是消息的有效载荷部分,它采用的是格式为“类型——长度——值”,简写TLV。

其中类型和长度使用属性头nlattr来表示。其中nla_len表示属性长度;nla_type表示属性类型,它可以取值为以下几种类型(定义在include\net\netlink.h中):

 /**
  * Standard attribute types to specify validation policy
  */
enum {
        NLA_UNSPEC,
        NLA_U8,
        NLA_U16,
        NLA_U32,
        NLA_U64,
        NLA_STRING,
        NLA_FLAG,
        NLA_MSECS,
        NLA_NESTED,
        NLA_NESTED_ARRAY,
        NLA_NUL_STRING,
        NLA_BINARY,
        NLA_S8,
        NLA_S16,
        NLA_S32,
        NLA_S64,
        NLA_BITFIELD32,
        NLA_REJECT,
        __NLA_TYPE_MAX,
};

其中比较常用的NLA_UNSPEC表示类型和长度未知、NLA_U32表示无符号32位整形数、NLA_STRING表示变长字符串、NLA_NESTED表示嵌套属性(即包含一层 新的属性)。

3、netlink有效性策略:struct nla_policy

// kernel/include/net/netlink.h

 * Example:
 *
 * static const u32 myvalidflags = 0xff231023;
 *
 * static const struct nla_policy my_policy[ATTR_MAX+1] = {
 *      [ATTR_FOO] = { .type = NLA_U16 },
 *      [ATTR_BAR] = { .type = NLA_STRING, .len = BARSIZ },
 *      [ATTR_BAZ] = NLA_POLICY_EXACT_LEN(sizeof(struct mystruct)),
 *      [ATTR_GOO] = NLA_POLICY_BITFIELD32(myvalidflags),
 * };
 */
struct nla_policy {
        u8              type;
        u8              validation_type;
        u16             len;
        union {
                const u32 bitfield32_valid;
                const u32 mask;
                const char *reject_message;
                const struct nla_policy *nested_policy;
                struct netlink_range_validation *range;
                struct netlink_range_validation_signed *range_signed;
                struct {
                        s16 min, max;
                };
                int (*validate)(const struct nlattr *attr,
                                struct netlink_ext_ack *extack);
                /* This entry is special, and used for the attribute at index 0
                 * only, and specifies special data about the policy, namely it
                 * specifies the "boundary type" where strict length validation
                 * starts for any attribute types >= this value, also, strict
                 * nesting validation starts here.
                 *
                 * Additionally, it means that NLA_UNSPEC is actually NLA_REJECT
                 * for any types >= this, so need to use NLA_POLICY_MIN_LEN() to
                 * get the previous pure { .len = xyz } behaviour. The advantage
                 * of this is that types not specified in the policy will be
                 * rejected.
                 *
                 * For completely new families it should be set to 1 so that the
                 * validation is enforced for all attributes. For existing ones
                 * it 
一、imp2源码 整个源码包含三个文件:imp2_k.c, imp2_u.c和imp2.h. 其中imp2_k.c为内核模块的源代码,imp2_u.c为应用程序,即测试代码,imp2.h为两个源文件都需要引用的头文件。其整体的功能是:注册一种新的netlink协议,并注册一个新的NF hook函数。当有ping包发往当前主机或者经过当前主机转发时,内核向用户发送ping包的源IP和目的IP。各个文件的简单分析见下文。 1. imp2.h 该文件主要是定义了一种新的Netlink协议类型NL_IMP2(31)。新的协议类型的选值不能和当前内核中已经定义的netlink协议类型重复。定义了基于该协议类型的消息类型,内核根据接收到消息的不同类型,进行不同的处理:IMP2_U_PID和IMP2_CLOSE分别为请求和关闭。IMP2_K_MSG代表内核空间发送的消息。 该头文件的源码如下: 2. imp2_k.c 该程序为内核模块程序。其完成的功能如下: (1)创建一种新的Netlink协议NL_IMP2,并注册该协议的回调函数kernel_receive。但用户空间通过建立且协议类型为NL_IMP2的socket套接字并调用sendto,sendmsg函数发送数据时,传送到内核空间的数据由kernel_receive进行处理。该函数主要是记录用户进程的ID,用于随后发送数据的时候指定目的。 (2)在Netfilter的hook点NF_IP_PRE_ROUTING注册hook函数get_icmp,对经过该hook点的ping包进行处理。get_icmp首先判断是否是ping包,如果不是,直接Accept。如果是,则记录该包的源IP和目的IP,然后调用send_to_user,将记录的信息发送给kernel_recieve函数中记录的用户进程ID。 该文件的源码如下: 3. imp2_u.c 该程序为用户空间的测试程序。该程序包括以下功能: (1)生成NL_IMP2协议的socket.然后通过调用sendto发送IMP2_U_PID类型的请求信息给内核。然后等待接受内核发回的信息。记住:仅当有ping包经过内核的NF时,内核才会向用户进程发送信息。 (2)当用户进程通过Ctrl+C来结束该程序时,调用信号处理函数sig_int,向内核发送IMP2_CLOSE的消息,结束socket。 该文件的源码如下: 二、编译和测试 1. 整个源文件编译的Makefile如下: all: gcc -O2 -DMODULE -D__KERNEL__ -W -Wstrict-prototypes -Wmissing-prototypes -isystem /lib/modules/`uname -r`/build/include -c -o imp2_k.o imp2_k.c gcc imp2_u.c -o imp2_u install: insmod imp2_k.o uninstall: rmmod imp2_k clean: rm -f imp2_k.o imp2_u 2. 加载内核模块,并执行测试程序。 #make install #./imp2_u 当没有ping包时,终端一直处于等待输出状态。通过另一台主机(192.168.1.100)向当前主机(192.168.1.101)发送ping包,则终端已经有输出: #./imp2_u [root@localhost imp2]# ./imp2_u src: 192.168.1.100, dest: 192.168.1.101 src: 192.168.1.100, dest: 192.168.1.101 src: 192.168.1.100, dest: 192.168.1.101 src: 192.168.1.100, dest: 192.168.1.101
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值