netlink原理及应用

1 篇文章 0 订阅
1 篇文章 0 订阅
本文详细介绍了netlink,一种基于网络的通信机制,它在内核与用户态应用间提供高效通信。文章探讨了netlink的优势、数据结构、内核核心函数,以及如何在内核中创建和使用netlinksocket,以及如何扩展自定义netlink协议,以nl80211为例。
摘要由CSDN通过智能技术生成

什么是netlink

netlink是一种基于网络的通信机制,允许内核内部、内核与用户态应用之间甚至用户态应用之间进行通信;netlink的主要作用是内核与用户态之间通信;它的思想是,基于BSD的socket使用网络框架在内核和用户态之间进行通信;

为什么要有netlink

内核中有其他一些方法可以实现用户空间和内核通信,如procfs、sysfs和ioctrl等;netlink相比于这些方法,有以下优势:

  • 任何一方都不需要轮询;如果通过文件通信,用户态应用需要不断检查是否有新消息到达;
  • netlink使用简单,它是基于socket的,可以使用socket api;
  • 只需要在netlink协议族中新增加一个协议;使用netlink的内核部分可以采用模块的方式实现,之后使用socket api进行通信;
  • 内核可以直接向用户层发送信息,而无需用户层事先请求;
  • netlink支持单播、组播;内核模块可以把消息发送到一个多播组;

数据结构

struct sockaddr_nl

netlink是基于网络的,使用socket通信;类似于其它网络协议,每个netlink socket都需要分配一个地址;struct sockaddr_nl表示netlink地址;

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,固定为AF_NETLINK,表示netlink协议族;

netlink协议族包含多个协议,最大值32;理论上32以内未被占用的协议号,可以用于自定义netlink协议,但这种方法并不规范,对于未来更新内核版本兼容性不友好;更加合适的方法,是在generic netlink协议族中,添加子协议,如nl80211就是generic netlink的一个子协议;

#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	
  • nl_pid,socket的唯一标识符;对内核自身来说,该字段是0,而用户空间的应用程序通常使用其线程组ID;netlink并没有要求该字段是进程ID,它可以是任何值,只需要保证其唯一性;使用线程组ID不过是方便而已;nl_pid是一个单播地址;
  • nl_groups,多播组掩码,每个bit表示一个多播组;每个netlink协议族最多支持32个多播组;

netlink内核核心函数

netlink_kernel_create

内核创建netlink socket;

static inline struct sock *
netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)
{
	return __netlink_kernel_create(net, unit, THIS_MODULE, cfg);
}
  • net,表示网络命令空间;
  • uint,表示netlink子协议族,如:
#define NETLINK_ROUTE		0	/* Routing/device hook				*/
#define NETLINK_GENERIC		16
  • cfg,netlink kernel创建socket的可选参数;其中,input是该内核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);
};

netlink消息格式

netlink消息由两部分组成:消息头和消息体;消息头固定为16字节,消息体长度可变;
image.png

消息头

消息头定义如下:

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_type,消息类型,netlink定义一下四种通用消息类型
#define NLMSG_NOOP		0x1	/* Nothing.		*/
#define NLMSG_ERROR		0x2	/* Error		*/
#define NLMSG_DONE		0x3	/* End of a dump	*/
#define NLMSG_OVERRUN		0x4	/* Data lost		*/

#define NLMSG_MIN_TYPE		0x10	/* < 0x10: reserved control messages */
  • nlmsg_flags,消息标志;如NLM_F_REQUEST
/* Flags values */

#define NLM_F_REQUEST		0x01	/* It is request message. 	*/
#define NLM_F_MULTI		0x02	/* Multipart message, terminated by NLMSG_DONE */
#define NLM_F_ACK		0x04	/* Reply with ack, with zero or error code */
#define NLM_F_ECHO		0x08	/* Echo this request 		*/
#define NLM_F_DUMP_INTR		0x10	/* Dump was inconsistent due to sequence change */
#define NLM_F_DUMP_FILTERED	0x20	/* Dump was filtered as requested */

/* Modifiers to GET request */
#define NLM_F_ROOT	0x100	/* specify tree	root	*/
#define NLM_F_MATCH	0x200	/* return all matching	*/
#define NLM_F_ATOMIC	0x400	/* atomic GET		*/
#define NLM_F_DUMP	(NLM_F_ROOT|NLM_F_MATCH)

/* Modifiers to NEW request */
#define NLM_F_REPLACE	0x100	/* Override existing		*/
#define NLM_F_EXCL	0x200	/* Do not touch, if it exists	*/
#define NLM_F_CREATE	0x400	/* Create, if it does not exist	*/
#define NLM_F_APPEND	0x800	/* Add to end of list		*/

/* Flags for ACK message */
#define NLM_F_CAPPED	0x100	/* request was capped */
#define NLM_F_ACK_TLVS	0x200	/* extended ACK TVLs were included */
  • nlmsg_seq,消息序列号,表示一系列消息之间在时间上的前后关系;也可以通过request消息和ack消息使用相同的序列号,保证消息不丢失;
  • nlmsg_pid,消息发送者的port id;

消息体

netlink协议并没有严格要求消息体的格式,可以发送任意消息;但一般标准做法,消息体是用nlattr,即属性,采用tlv的形式;消息体组织形式如下:
image.png

struct nlattr定义如下:

/*
 *  <------- NLA_HDRLEN ------> <-- NLA_ALIGN(payload)-->
 * +---------------------+- - -+- - - - - - - - - -+- - -+
 * |        Header       | Pad |     Payload       | Pad |
 * |   (struct nlattr)   | ing |                   | ing |
 * +---------------------+- - -+- - - - - - - - - -+- - -+
 *  <-------------- nlattr->nla_len -------------->
 */

struct nlattr {
	__u16           nla_len;
	__u16           nla_type;
};

netlink协议族组织形式

netlink协议族、子协议族、子协议、命令,组织结构如下:
image.png

如何新增netlink子协议族

如何将自定义netlink协议加入到netlink协议族中,于NETLINK_GENERIC同一级别?只需定义一个netlink协议号即可,由于netlink对消息体格式不做强制要求,可以传输简单的字符串;实际使用中,不建议这样做,但作为学习,可以简单的这样操作;实际使用中增加自定义netlink协议,建议加入到NETLINK_GENERIC协议族中,类似nl80211这样;
下面代码,是直接在netlink中直接加入新的协议,定义协议号为30;内核中新增一个模块,处理该协议的消息;应用程序通过该协议,和内核通信;简单起见,直接传输字符串;应用程序先向内核发送一条消息,内核收到消息后进行回复;

内核代码

内核代码如下:


#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <net/sock.h>
#include <linux/netlink.h>

#define NETLINK_TEST     30
#define MSG_LEN            125

MODULE_LICENSE("GPL");

struct sock *nlsk = NULL;
extern struct net init_net;

int send_usrmsg(char *pbuf, uint16_t len, uint32_t pid)
{
    struct sk_buff *nl_skb;
    struct nlmsghdr *nlh;

    int ret;

    /* Allocate a new netlink message */
    nl_skb = nlmsg_new(len + 1, GFP_ATOMIC);
    if(!nl_skb)
    {
        printk("\nError:netlink alloc failure.\n\n");
        return -1;
    }

    /* Add a new netlink message to an skb
        pid是0,说明是从内核发送的
    */
    nlh = nlmsg_put(nl_skb, 0, 0, NETLINK_TEST, len, 0);
    if(nlh == NULL)
    {
        printk("\nError:nlmsg_put failaure. \n\n");
        nlmsg_free(nl_skb);
        return -1;
    }

    /* copy payload */
    memcpy(nlmsg_data(nlh), pbuf, len);
    ret = netlink_unicast(nlsk, nl_skb, pid, MSG_DONTWAIT);

    return ret;
}

static void netlink_rcv_msg(struct sk_buff *skb)
{
    struct nlmsghdr *nlh = NULL;
    char *umsg = NULL;
    char *kmsg = "Hello user's program.";

    if(skb->len >= nlmsg_total_size(0))
    {
        nlh = nlmsg_hdr(skb);
        umsg = NLMSG_DATA(nlh);
        if(umsg)
        {
            printk("kernel recv from user space: %s\n", umsg);
            send_usrmsg(kmsg, strlen(kmsg), nlh->nlmsg_pid);
        }
    }
}

struct netlink_kernel_cfg cfg = {
        .input  = netlink_rcv_msg, /* set recv callback */
};

int test_netlink_init(void)
{
    /* create netlink socket */
    nlsk = (struct sock *)netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);
    if(nlsk == NULL)
    {
        printk("\nError:netlink_kernel_create error !\n");
        return -1;
    }
    printk("\ntest_netlink_init\n");

    return 0;
}

void test_netlink_exit(void)
{
    if (nlsk){
        netlink_kernel_release(nlsk); /* release ..*/
        nlsk = NULL;
    }
    printk("test_netlink_exit!\n");
}

module_init(test_netlink_init);
module_exit(test_netlink_exit);
#
#Desgin of Netlink
#
MODULE_NAME :=nl_test_kernel
obj-m:=$(MODULE_NAME).o
	

KERNELDIR ?=/lib/modules/$(shell uname -r)/build
PWD :=$(shell pwd)



all:

	$(MAKE) -C $(KERNELDIR) M=$(PWD)

clean:

	$(MAKE) -C $(KERNELDIR) M=$(PWD) clean

nl_test_kernel.cMakefile放到同一目录下;直接make,编译生成nl_test_kernel.ko
insmod nl_test_kernel.ko,将该模块加载到内核中;内核现在就可以处理NETLINK_TEST的消息了;
image.png

应用程序代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <linux/netlink.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>

#define NETLINK_TEST    30
#define MSG_LEN         125
#define MAX_PLOAD       125

typedef struct _user_msg_info
{
    struct nlmsghdr hdr;
    char  msg[MSG_LEN];
} user_msg_info;

int main(int argc, char **argv)
{
    int skfd;
    int ret;
    user_msg_info u_info;
    socklen_t len;
    struct nlmsghdr *nlh = NULL;
    struct sockaddr_nl saddr, daddr;
    char *umsg = "Hello Netlink protocol.";

    /* 创建NETLINK socket */
    skfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
    if(skfd == -1)
    {
        perror("\nError:Create socket error.\n");
        return -1;
    }

    memset(&saddr, 0, sizeof(saddr));
    saddr.nl_family = AF_NETLINK; //AF_NETLINK
    saddr.nl_pid = getpid();  //端口号(port ID)
    saddr.nl_groups = 0;
    if(bind(skfd, (struct sockaddr *)&saddr, sizeof(saddr)) != 0)
    {
        perror("\nError:bind() error.\n");
        close(skfd);
        return -1;
    }

    memset(&daddr, 0, sizeof(daddr));
    daddr.nl_family = AF_NETLINK;
    daddr.nl_pid = 0; // to kernel
    daddr.nl_groups = 0;

    nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PLOAD));
    memset(nlh, 0, sizeof(struct nlmsghdr));
    nlh->nlmsg_len = NLMSG_SPACE(MAX_PLOAD);
    nlh->nlmsg_flags = 0;
    nlh->nlmsg_type = 0;
    nlh->nlmsg_seq = 0;
    nlh->nlmsg_pid = saddr.nl_pid; //self port

    memcpy(NLMSG_DATA(nlh), umsg, strlen(umsg));
    ret = sendto(skfd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&daddr, sizeof(struct sockaddr_nl));
    if(!ret)
    {
        perror("\nError:sendto error.\n");
        close(skfd);
        exit(-1);
    }
    printf("\nApplication-->Send to kernel:%s\n\n", umsg);

    memset(&u_info, 0, sizeof(u_info));
    len = sizeof(struct sockaddr_nl);
    ret = recvfrom(skfd, &u_info, sizeof(user_msg_info), 0, (struct sockaddr *)&daddr, &len);
    if(!ret)
    {
        perror("\nError:recv form kernel error.\n");
        close(skfd);
        exit(-1);
    }

    printf("\nApplication-->From kernel:%s\n\n", u_info.msg);
    close(skfd);

    free((void *)nlh);
    return 0;
}

gcc -o nl_test_user nl_test_user.c

测试结果

image.png

如何新增自定义netlink协议

如何在NETLINK_GENERIC中新增netlink协议?
参考nl80211

模块初始化时,通过genl_register_family注册通用netlink协议族,将命令以及处理函数进行注册;

/* initialisation/exit functions */

int __init nl80211_init(void)
{
	int err;

	err = genl_register_family(&nl80211_fam);
	if (err)
		return err;

	err = netlink_register_notifier(&nl80211_netlink_notifier);
	if (err)
		goto err_out;

	return 0;
 err_out:
	genl_unregister_family(&nl80211_fam);
	return err;
}
/**
 * genl_register_family - register a generic netlink family
 * @family: generic netlink family
 *
 * Registers the specified family after validating it first. Only one
 * family may be registered with the same family name or identifier.
 *
 * The family's ops, multicast groups and module pointer must already
 * be assigned.
 *
 * Return 0 on success or a negative error code.
 */
int genl_register_family(struct genl_family *family)
static const struct genl_ops nl80211_ops[] = {
	{
		.cmd = NL80211_CMD_GET_WIPHY,
		.doit = nl80211_get_wiphy,
		.dumpit = nl80211_dump_wiphy,
		.done = nl80211_dump_wiphy_done,
		.policy = nl80211_policy,
		/* can be retrieved by unprivileged users */
		.internal_flags = NL80211_FLAG_NEED_WIPHY |
				  NL80211_FLAG_NEED_RTNL,
	},
	{
		.cmd = NL80211_CMD_SET_WIPHY,
		.doit = nl80211_set_wiphy,
		.policy = nl80211_policy,
		.flags = GENL_UNS_ADMIN_PERM,
		.internal_flags = NL80211_FLAG_NEED_RTNL,
	},
    ......
}
NetlinkLinux 内核提供的一种机制,用于内核与用户空间之间的通信。通过 Netlink应用程序可以向内核发送请求,并接收内核的相应信息。 使用 Netlink应用程序通信需要以下步骤: 1. 创建 Netlink socket:使用 `socket()` 系统调用创建一个 Netlink socket。 2. 绑定 Netlink socket:使用 `bind()` 系统调用将 Netlink socket 绑定到一个本地地址上。 3. 发送请求:使用 `sendmsg()` 系统调用向内核发送请求消息。 4. 接收响应:使用 `recvmsg()` 系统调用从 Netlink socket 接收响应消息。 5. 处理响应:对接收到的响应消息进行解析和处理。 下面是一个简单的示例代码,演示如何使用 Netlink 与内核模块进行通信: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/socket.h> #include <linux/netlink.h> #define NETLINK_USER 31 #define MAX_PAYLOAD 1024 /* maximum payload size*/ struct nlmsg { struct nlmsghdr hdr; char data[MAX_PAYLOAD]; }; int main(int argc, char **argv) { int sock_fd; struct sockaddr_nl src_addr, dest_addr; struct nlmsg msg; struct iovec iov; struct msghdr mh; /* create netlink socket */ sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_USER); if (sock_fd < 0) { perror("socket"); return -1; } /* initialize source address */ memset(&src_addr, 0, sizeof(src_addr)); src_addr.nl_family = AF_NETLINK; src_addr.nl_pid = getpid(); /* bind netlink socket */ if (bind(sock_fd, (struct sockaddr *)&src_addr, sizeof(src_addr)) < 0) { perror("bind"); close(sock_fd); return -1; } /* initialize destination address */ memset(&dest_addr, 0, sizeof(dest_addr)); dest_addr.nl_family = AF_NETLINK; dest_addr.nl_pid = 0; /* kernel */ dest_addr.nl_groups = 0; /* unicast */ /* initialize netlink message */ memset(&msg, 0, sizeof(msg)); msg.hdr.nlmsg_len = NLMSG_LENGTH(strlen("Hello, kernel!") + 1); msg.hdr.nlmsg_type = 1; msg.hdr.nlmsg_flags = NLM_F_REQUEST; strcpy(msg.data, "Hello, kernel!"); /* initialize iovec */ iov.iov_base = &msg.hdr; iov.iov_len = msg.hdr.nlmsg_len; /* initialize msghdr */ memset(&mh, 0, sizeof(mh)); mh.msg_name = &dest_addr; mh.msg_namelen = sizeof(dest_addr); mh.msg_iov = &iov; mh.msg_iovlen = 1; /* send netlink message */ if (sendmsg(sock_fd, &mh, 0) < 0) { perror("sendmsg"); close(sock_fd); return -1; } /* receive netlink message */ memset(&msg, 0, sizeof(msg)); iov.iov_base = &msg; iov.iov_len = sizeof(msg); if (recvmsg(sock_fd, &mh, 0) < 0) { perror("recvmsg"); close(sock_fd); return -1; } /* print netlink message */ printf("Received message: %s\n", msg.data); close(sock_fd); return 0; } ``` 在上面的示例中,我们首先创建了一个 Netlink socket,并将其绑定到一个本地地址上。然后,我们使用 `sendmsg()` 向内核发送一个请求消息,该消息包含一个字符串 "Hello, kernel!"。最后,我们使用 `recvmsg()` 接收内核的响应消息,并将其打印出来。 当然,这只是一个简单的示例,实际使用中可能需要更复杂的消息格式和处理逻辑。但是,基本的 Netlink 通信流程和 API 调用方式应该是类似的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值