netlink机制

简介:

netlink是一种基于网络的机制,允许在内核内部以及内核与用户层之间进行通信。最早在内核2.2引入,旨在替代笨拙的IOCTL,IOCTL不能从内核向用户空间发送异步消息,而且必须定义IOCTL号。

Netlink协议定义在RFC3549中。以前是可以编译成模块,现在直接集成到内核了。与profs和sysfs相比,有一些优势如下:

不需要轮询;系统调用和ioctl也能从用户层想内核传递信息,但是难以实现,另外netlink不会和模块冲突;内核可以直接向用户层发送信息;使用标准的套接字即可。

/proc/net/netlink文件中包含了当前活动的netlink连接信息。

            代码位于net/netlink中。

af_netlink.c  af_netlink.h  diag.c  genetlink.c

            其中genetlink提供通用的Netlink API,af_netlink提供了套接字API,diag是监视接口提供用于读写有关Netlink套接字的信息。

1.1.1 Netlink子系统初始化

通过函数netlink_proto_init(net/netlink/af_netlink.c)向内核注册协议。

static int __init netlink_proto_init(void)

{

        int i;

        int err = proto_register(&netlink_proto, 0);

 

        if (err != 0)

                goto out;

 

        BUILD_BUG_ON(sizeof(struct netlink_skb_parms) > FIELD_SIZEOF(struct sk_buff, cb));

 

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

        if (!nl_table)

                goto panic;

 

        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_add_usersock_entry();

 

        sock_register(&netlink_family_ops);

        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");

}

创建并初始化了nl_table表数组,这个表是整个netlink实现的关键一步,每种协议类型占数组中的一项,后续内核中创建的不同种协议类型的netlink都将保存在这个表中,由该表统一维护,该表结构如下:

struct netlink_table {  

        struct rhashtable       hash;

        struct hlist_head       mc_list;

        struct listeners __rcu  *listeners;

        unsigned int            flags;

        unsigned int            groups;

        struct mutex            *cb_mutex;

        struct module           *module;                 

        int                     (*bind)(struct net *net, int group);

        void                    (*unbind)(struct net *net, int group);

        bool                    (*compare)(struct net *net, struct sock *sock);                                                                                        

        int                     registered;                                                                                                                            

};

            然后调用sock_register(源码:sock_register(&netlink_family_ops);)向内核注册协议处理函数,即将netlink的socket创建处理函数注册到内核中,以后应用层创建netlink类型的socket时将会调用该协议处理函数,其中netlink_family_ops函数的定义如下,可以知道每次创建PF_NETLINK(AF_NETLINK)类型的socket()系统调用时,将由netlink_create()函数负责处理。

static const struct net_proto_family netlink_family_ops = {

        .family = PF_NETLINK,

        .create = netlink_create,

        .owner  = THIS_MODULE,  /* for consistency 8) */

};

继续调用register_pernet_subsys

        register_pernet_subsys(&netlink_net_ops);

        register_pernet_subsys(&netlink_tap_net_ops);

该函数向内核的网络命名空间注册子系统的初始化和析构函数,在网络命名空间创建和注销时会调用这里注册的初始化和析构函数。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()就是则销毁。

最后调用rtnetlink_init()创建NETLINK_ROUTE协议类型的Netlink,用来传递网络路由子系统、邻居子系统、接口设置、防火墙等消息。整个Netlink子系统初始化完成

 

1.1.2 使用

Netlink套接字可以是SOCK_RAW套接字,也可以是SOCK_DGRAM套接字。内核和用户空间都可以使用Netlink套接字,只是调用的方法不同,用户空间使用传统的socket系统调用,内核态使用netlink_kernel_create函数。最终都会调用__netlink_create方法。

1b0813f287a8b5f914072a8ccbf92b2573e19959

然后创建一个sockaddr_nl结构来表示用户空间或内核Netlink套接字的地址。

开发使用Netlink套接字来收发数据的用户空间应用程序时,推荐使用libnl API。Libnl包还包含支持通用Netlink簇、路由选择簇和Netfilter簇的库。

            Netlink套接字不仅用于网络子系统,还用于其他子系统如:SELinux、audit、uevent等。

            Netlink采用地址编码,struct sockaddr_nl,每个通过netlink发出的消息都必须附带一个netlink自己的消息头(struct nlmsghdr)。

            下面来看下相关的数据结构。

1.1.3 数据结构

所有socket之间的通信,必须有个地址结构,netlink的地址结构如下:

sockaddr_nl定义在include/uapi/linux/netlink.h文件中。

struct sockaddr_nl {

        __kernel_sa_family_t    nl_family;      /* AF_NETLINK   */

        unsigned short  nl_pad;         /* zero         */       

        __u32           nl_pid;         /* port ID      */       

        __u32           nl_groups;      /* multicast groups mask */

};

最关键的是nl_family,所支持的定义在include/uapi/linux/netlink.h在内核网络栈中,可创建多种Netlink套接字,每种内核套接字可处理不同的类型消息。例如,NETLINK_ROUTE消息,通过和NETLINK_ROUTE协议通信,可以获得内核的路由信息

…..

#define NETLINK_CRYPTO          21      /* Crypto layer */

#define NETLINK_SMC             22      /* SMC monitoring */

  现在支持到了23个族。

Netlink_kernel_cfg结构体包含用于创建Netlink套接字的可选参数, 是内核netlink配置结构。

/* 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);

};

每个协议族都需要在内核中注册一个net_proto_family实例。

该结构体如下:

struct net_proto_family {

        int             family;

        int             (*create)(struct net *net, struct socket *sock,

                                  int protocol, int kern);       

        struct module   *owner;        

}; 

 

 

1.1.4 消息格式

在用户空间和内核空间进行交换时候,必须采用特定的格式。消息的开头是长度固定的netlink报头。

报头的结构体为nlmsghdr结构体,共16个字节。

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 */

};

            整个消息长度,包括首部和任何所需的填充字节,在nlmsg_len。nlmsg_pid是发送消息的用户程序进程PID。nlmsg_seq序列号,用于排列消息,并不是必须的。nlmsg_flags例如是NLM_F_REQUEST。nlmsg_type表示消息类型,如NLMMSG_ERROR发生了错误。

netlink的消息头后面跟着的是消息的有效载荷部分,它采用的是格式为“类型——长度——值”,简写TLV。其中类型和长度使用属性头nlattr来表示。其中nla_len表示属性长度;nla_type表示属性类型,取值定义在include\net\netlink.h中。

 netlink属性头是struct nlattr,定义在include/uapi/linux/netlink.h

struct nlattr {

        __u16           nla_len;

        __u16           nla_type;

};

1.1.5 关于使用

使用Netlink的方法如下,先运行netlink内核模块;然后运行用户态程序,向内核发送连接消息,通知内核用户的进程id;内核接收用户消息,记录其进程id;内核向用户进程id发送netlink消息;用户接收内核发送的netlink消息。Ok,整体流程这样。

此外,为了获取netlink报文中数据的方便,netlink提供了下面几个宏进行数据的获取和解包操作,定义在include/uapi/linux/netlink.h

#define NLMSG_ALIGNTO   4U

#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )

#define NLMSG_HDRLEN     ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))

#define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)

#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))

#define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))

#define NLMSG_NEXT(nlh,len)      ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \

                                  (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))

#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \

                           (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \

                           (nlh)->nlmsg_len <= (len))

#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))

从用户空间接收的数据将由netlink_kernel_cfg结构体中的input指定函数来处理。

目录
相关文章
01 Packet Tracer 网络通信过程
01 Packet Tracer 网络通信过程
55 0
|
1月前
|
网络协议 算法 数据库
OSPF协议详解:工作原理与实现机制
OSPF协议详解:工作原理与实现机制
147 0
|
3月前
|
监控 网络协议 UED
TCP协议中的两种保活机制详述
TCP的保活机制通过保活探针和用户配置的保活时间两种方式,为网络通讯提供了重要的保障。它帮助识别并处理那些因为网络不稳定或对端突然下线而变得无响应的连接,对于确保长时间运行的网络应用的稳定性和可靠性非常关键。合理配置和使用TCP保活机制,可以显著提升网络应用的鲁棒性和用户体验。
130 1
|
4月前
|
缓存 网络协议
用户态协议栈02-arp reply实现
用户态协议栈02-arp reply实现
|
4月前
|
负载均衡 网络协议 Linux
在Linux中,如何理解VRRP协议?
在Linux中,如何理解VRRP协议?
|
7月前
|
网络协议 Linux
Linux内核源码剖析之TCP保活机制(KeepAlive)
总之,TCP保活机制通过定期发送保活探测报文,以检测空闲连接是否仍然活跃。这种机制在网络通信中有助于及时检测和关闭不再使用的连接,从而节省资源并提高连接的可靠性。
221 0
|
7月前
|
存储 传感器 网络协议
通信协议缓冲区管理全景:TCP、UDP、ZMQ、DBus、SSL、SOME/IP通讯协议的缓冲区解析...
通信协议缓冲区管理全景:TCP、UDP、ZMQ、DBus、SSL、SOME/IP通讯协议的缓冲区解析...
332 0
|
7月前
|
网络协议 API 网络安全
用户态协议栈设计实现udp,arp与icmp协议
用户态协议栈设计实现udp,arp与icmp协议
157 1
|
7月前
|
网络协议 Linux C语言
用户态协议栈设计netmap实现
用户态协议栈设计netmap实现
74 0
|
7月前
|
网络协议
netmap: UDP 协议栈的实现
netmap: UDP 协议栈的实现