linux下netlink的使用简介

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux下netlink的使用简介相关的知识,希望对你有一定的参考价值。

参考技术A

Netlink套接字是用以实现 用户进程 内核进程 通信的一种特殊的进程间通信(IPC) ,也是网络应用程序与内核通信的最常用的接口。

在Linux 内核中,使用netlink 进行应用与内核通信的应用有很多,如

Netlink 是一种在内核与用户应用间进行双向数据传输的非常好的方式,用户态应用使用标准的 socket API 就可以使用 netlink 提供的强大功能,内核态需要使用专门的内核 API 来使用 netlink。

一般来说用户空间和内核空间的通信方式有三种: /proc、ioctl、Netlink 。而前两种都是单向的,而Netlink可以实现双工通信。

Netlink 相对于系统调用,ioctl 以及 /proc文件系统而言,具有以下优点:

Netlink协议基于BSD socket和 AF_NETLINK 地址簇,使用32位的端口号寻址,每个Netlink协议通常与一个或一组内核服务/组件相关联,如 NETLINK_ROUTE 用于获取和设置路由与链路信息、 NETLINK_KOBJECT_UEVENT 用于内核向用户空间的udev进程发送通知等。

用户态应用使用标准的 socket API有sendto(),recvfrom(), sendmsg(), recvmsg()。

Netlink通信跟常用UDP Socket通信类似, struct sockaddr_nl 是netlink通信地址,跟普通 socket struct sockaddr_in 类似。

netlink_kernel_create内核函数用于创建内核socket与用户态通信

首先将编译出来的Netlink内核模块插入到系统当中(insmod netlink_test.ko),然后运行应用程序,可以看到如下输出:

Linux用户与内核空间交互—netlink

目录

简介

一、netlink soket

优点

二、用户空间

1、API

2、编程流程

3、源码

三、内核编程

1、API

2、内核空间编程流程

3、内核源码


简介

用户空间与内核的交互方式,使用copy_from_user(), copy_to_user().

除了这两种交互方式,内核还提供了其他高级的方式,对于写驱动来说很重要。有proc、sysfs、debugfs、netlink、ioctl。

本文学习netlink

一、netlink soket

优点

  • 双向同步传输数据;
  • 高速数据传输,特别是在本地主机上;
  • 异步通信;消息可以排队,因此发送方不必等待接收方;
  • 在其他用户到内核通信方法(如procfs、sysfs、debugfs和ioctl)中,用户空间程序必须开始传输到内核空间的;而使用netlink套接字,内核可以起始传输到用户空间;
  • procfs、sysfs、debugfs中可能引起域名污染;对于netlink套接字,情况并非如此,因为没有文件。

二、用户空间

1、API

socket(PF_NETLINK, SOCK_RAW, NETLINK_MY_UNIT_PROTO)

2、编程流程

  1. 创建netlink;socket(PF_NETLINK, SOCK_RAW, NETLINK_MY_UNIT_PROTO);域名 PF_NETLINK,类型SOCK_RAW,协议 NETLINK_MY_UNIT_PROTO
  2. 初始化netlink源的sockerddr; bind socket
  3. 初始化netlink目的地址结构。目的地址设置成0表示是内核;
  4. 初始化netlink的头数据结构,指定源PID,数据负载;
  5. 初始化iovec结构,指向引用netlink头,初始化msghdr 数据结构,指向目的地址和iov
  6. 数据的发送sendmsg,数据的接收recvmsg

3、源码

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

#define NETLINK_MY_UNIT_PROTO  31

#define NLSPACE              1024

//数据
static const char *thedata = "hello world";

int main(int argc, char **argv)

	int sd;
	struct sockaddr_nl src_nl, dest_nl;
	struct nlmsghdr *nlhdr; // 'carries' the payload
	struct iovec iov;
	struct msghdr msg;
	ssize_t nsent, nrecv;

    //、创建节点,域名PF_NETLINK,类型SOCK_RAW,协议NETLINK_MY_UNIT_PROTO
	sd = socket(PF_NETLINK, SOCK_RAW, NETLINK_MY_UNIT_PROTO);
	if (sd < 0) 
		exit(EXIT_FAILURE);
	

	//2、设置netlink源地址结构体已经绑定
	memset(&src_nl, 0, sizeof(src_nl));
	src_nl.nl_family = AF_NETLINK;
	src_nl.nl_pid = getpid();
	src_nl.nl_groups = 0x0;   // no multicast
	if (bind(sd, (struct sockaddr *)&src_nl, sizeof(src_nl)) < 0) 
		exit(EXIT_FAILURE);
	
		
	/* 3. 设置目的地址结构体*/
	memset(&dest_nl, 0, sizeof(dest_nl));
	dest_nl.nl_family = AF_NETLINK;
	dest_nl.nl_groups = 0x0; // no multicast
	dest_nl.nl_pid = 0;      // destined for the kernel

	/* 4. 分配netlink头内存*/
	nlhdr = (struct nlmsghdr *)malloc(NLMSG_SPACE(NLSPACE));
	if (!nlhdr) 
		exit(EXIT_FAILURE);
	
	memset(nlhdr, 0, NLMSG_SPACE(NLSPACE));
	nlhdr->nlmsg_len = NLMSG_SPACE(NLSPACE);
	nlhdr->nlmsg_pid = getpid();

	/* 设置payload  thedata是待发送的数据*/
	strncpy(NLMSG_DATA(nlhdr), thedata, strnlen(thedata, NLSPACE)+1);

	/* 5.设置iovec*/
	memset(&iov, 0, sizeof(struct iovec));
	iov.iov_base = (void *)nlhdr;//上面申请的空间
	iov.iov_len = nlhdr->nlmsg_len;

	/* 设置消息头结构体 */
	memset(&msg, 0, sizeof(struct msghdr));
	msg.msg_name = (void *)&dest_nl;   // 目的地址
	msg.msg_namelen = sizeof(dest_nl); // 目的地址大学
	msg.msg_iov = &iov;//
	msg.msg_iovlen = 1; // # elements in msg_iov

	
	/* 6. sendmsg */
	nsent = sendmsg(sd, &msg, 0);
	if (nsent < 0) 
		free(nlhdr);
		exit(EXIT_FAILURE);
	 else if (nsent == 0) 
		free(nlhdr);
		exit(EXIT_FAILURE);
	

	printf("%s:sendmsg(): *** success, sent %ld bytes all-inclusive\\n"
		   " (see kernel log for dtl)\\n", argv[0], nsent);

	fflush(stdout);

	/* 7. 阻塞接收数据*/
	printf("%s: now blocking on kernel netlink msg via recvmsg() ...\\n", argv[0]);
	nrecv = recvmsg(sd, &msg, 0);
	if (nrecv < 0) 
		free(nlhdr);
		exit(EXIT_FAILURE);
	
	printf("%s:recvmsg(): *** success, received %ld bytes:"
		"\\nmsg from kernel netlink: \\"%s\\"\\n",
		argv[0], nrecv, (char *)NLMSG_DATA(nlhdr));

	free(nlhdr);
	close(sd);
	exit(EXIT_SUCCESS);

三、内核编程

1、API

struct sock * netlink_kernel_create(struct net *, int , struct netlink_kernel_cfg *);

2、内核空间编程流程

  1. netlink_kernel_create 创建netlink
  2. nlmsg_new()分配netlink内存,nlmsg_put()添加netlink信息到分配的内存;借助nlmsg_data()
    拷贝数据;
  3. nlmsg_unicast()发送内核数据到用户空间;
  4. netlink_kernel_release() 释放netlink

3、内核源码

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

MODULE_AUTHOR("wy");
MODULE_LICENSE("Dual MIT/GPL");
MODULE_VERSION("0.1");

//文件名
#define OURMODNAME   "netlink_simple_intf"
#define NETLINK_MY_UNIT_PROTO   31
#define NLSPACE              1024

static struct sock *nlsock;

//收到数据,触发回调函数。
static void netlink_recv_and_reply(struct sk_buff *skb)

	struct nlmsghdr *nlh;
	struct sk_buff *skb_tx;

    //返回的数据
	char *reply = "Reply from kernel netlink";
	int pid, msgsz, stat;

    //发生在进程上下文,而不是中断上下文
	PRINT_CTX();

	nlh = (struct nlmsghdr *)skb->data;
	pid = nlh->nlmsg_pid;	//发送进程的PID
	pr_info("received from PID %d:\\n" "\\"%s\\"\\n", pid, (char *)NLMSG_DATA(nlh));

    //返回数据长度
	msgsz = strnlen(reply, NLSPACE);
    //新netlink申请
	skb_tx = nlmsg_new(msgsz, 0);
	if (!skb_tx) 
		pr_warn("skb alloc failed!\\n");
		return;
	

	// 设置 payload
	nlh = nlmsg_put(skb_tx, 0, 0, NLMSG_DONE, msgsz, 0);
	NETLINK_CB(skb_tx).dst_group = 0; 

	//将发送的数据reply,使用nlmsg_data拷贝到netlink 
    strncpy(nlmsg_data(nlh), reply, msgsz);

	// 发送
	stat = nlmsg_unicast(nlsock, skb_tx, pid);
	if (stat < 0)
		pr_warn("nlmsg_unicast() failed (err=%d)\\n", stat);
	pr_info("reply sent\\n");


//当用户空间向kernel发数据,将触发此回调函数
static struct netlink_kernel_cfg nl_kernel_cfg = 
	.input = netlink_recv_and_reply,
;

static int __init netlink_simple_intf_init(void)

    //创建netlink
	nlsock = netlink_kernel_create(&init_net, NETLINK_MY_UNIT_PROTO,&nl_kernel_cfg);
	if (!nlsock) 
		pr_warn("netlink_kernel_create failed\\n");
		return PTR_ERR(nlsock);
	

	pr_info("inserted\\n");
	return 0;		/* success */


//模块退出清理
static void __exit netlink_simple_intf_exit(void)

	netlink_kernel_release(nlsock);
	pr_info("removed\\n");


module_init(netlink_simple_intf_init);
module_exit(netlink_simple_intf_exit);

四、串口输出

kernel

[  310.858620] netlink_simple_intf: loading out-of-tree module taints kernel.
[  310.858650] netlink_simple_intf: module verification failed: signature and/or required key missing - tainting kernel
[  310.860505] netlink_simple_intf:netlink_simple_intf_init(): creating kernel netlink socket
[  310.860510] netlink_simple_intf:netlink_simple_intf_init(): inserted
[  320.874385] netlink_simple_intf:netlink_recv_and_reply(): 005)  netlink_userapp :3730   |  ...0   /* netlink_recv_and_reply() */
[  320.874391] netlink_simple_intf:netlink_recv_and_reply(): received from PID 3730:
               "sample user data to send to kernel via netlink"
[  320.874394] netlink_simple_intf:netlink_recv_and_reply(): reply sent

app

./netlink_userapp:PID 3730: netlink socket created
./netlink_userapp: bind done
./netlink_userapp: destination struct, netlink hdr, payload setup
./netlink_userapp: initialized iov structure (nl header folded in)
./netlink_userapp: initialized msghdr structure (iov folded in)
./netlink_userapp:sendmsg(): *** success, sent 1040 bytes all-inclusive
 (see kernel log for dtl)
./netlink_userapp: now blocking on kernel netlink msg via recvmsg() ...
./netlink_userapp:recvmsg(): *** success, received 44 bytes:
msg from kernel netlink: "Reply from kernel netlink"

参考:

https://course.0voice.com/v1/course/intro?courseId=2&agentId=0

以上是关于linux下netlink的使用简介的主要内容,如果未能解决你的问题,请参考以下文章

Linux用户与内核空间交互—netlink

PF_NETLINK应用实例NETLINK_KOBJECT_UEVENT具体实现--udev实现原理

linux 内核中Netlink

linux 内核与用户空间通信之netlink使用方法

linux netlink套接字学习资料

在 Linux 内核中使用 netlink 套接字在用户空间应用程序和字符设备之间进行通信时出错