当前位置:   article > 正文

Linux Netlink的使用方法_linux4.9 netlink 例程

linux4.9 netlink 例程

Linux Netlink的使用方法

一、Netlink用户态构建过程

1、创建一个基于自定义端口NETLINK_TEST的socket。

#define NETLINK_TEST 99

skfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST); //Create a socket using user defined protocol NETLINK_TEST.
if (skfd == -1) {
	perror("create socket error\n");
	return -1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

2、初始化源地址结构struct sockaddr_nl用于数据传输

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

struct sockaddr_nl saddr;
//Source address.
memset(&saddr, 0, sizeof(saddr));
saddr.nl_family = AF_NETLINK; //AF_NETLINK
saddr.nl_pid = USER_PORT;  //netlink portid, same as kernel.
saddr.nl_groups = 0;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

该结构用于构建netlink的通信地址,类似于socket编程中的sockaddr_in类似,nl_pid用来表示通信端口,nl_groups用来表示通信组,注意这里为希望加入多播组的号的掩码,也就是最多支持32个组。

3**、初始化源地址和建立套接字文件描述符成功后,需要对套接字进行地址和端口的绑定,才能进行数据的接收和发送操作。**

//bind to skfd with saddr.
if (bind(skfd, (struct sockaddr *)&saddr, sizeof(saddr)) != 0) {
	perror("bind() error\n");
	close(skfd);
	return -1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

4、构造destination addr,即初始化结构为struct sockaddr_nl类型目的地址。

//Destination address.
memset(&daddr, 0, sizeof(daddr));
daddr.nl_family = AF_NETLINK;
daddr.nl_pid = 0;    // to kernel 
daddr.nl_groups = 0;
  • 1
  • 2
  • 3
  • 4
  • 5

5、构造Netlink的消息头,用struct nlmsghdr来描述,Netlink报文的数据区由消息头和消息体构成,消息体接在消息头之后。

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 */
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

以下的netlink常用的宏

#define NLMSG_ALIGNTO    4U
//用于得到不小于len且字节对齐的最小数值
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
//netlink头部长度
#define NLMSG_HDRLEN     ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
//计算消息数据len的真实消息长度,消息体+消息头
#define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)
//返回不小于NLMSG_LENGTH(len)且字节对齐的最小数值
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
//用于取得消息的数据部分的首地址,设置和读取消息数据部分时需要使用该宏
#define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
//用于得到下一个消息的首地址,同时len变为剩余消息的长度
#define NLMSG_NEXT(nlh,len)     ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
                  (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))
//判断消息是否>len
#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \
               (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \
               (nlh)->nlmsg_len <= (len))
//用于返回payload的长度
#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
//为消息头和消息体分配内存并初始化Netlink消息头
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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

6、netlink报文消息体初始化,也就是我们需要传输的数据。

memcpy(NLMSG_DATA(nlh), umsg, strlen(umsg));
  • 1

7、发送数据和接收数据

通过skfd向内核发送数据。

//包含头文件
#include <sys/types.h>
#include <sys/socket.h>

//函数定义
int sendto(int s, const void * msg, int len, unsigned int flags, const struct sockaddr * to, int tolen);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

**函数说明:**sendto() 用来将数据由指定的socket 传给对方主机. 参数s 为已建好连线的socket, 如果利用UDP协议则不需经过连线操作. 参数msg 指向欲连线的数据内容, 参数flags 一般设0, 详细描述请参考send(). 参数to 用来指定欲传送的网络地址, 结构sockaddr 请参考bind(). 参数tolen 为sockaddr 的结果长度.

返回值:成功则返回实际传送出去的字符数, 失败返回-1, 错误原因存于errno 中.

错误代码:
1、EBADF 参数s 非法的socket 处理代码.
2、EFAULT 参数中有一指针指向无法存取的内存空间.
3、WNOTSOCK canshu s 为一文件描述词, 非socket.
4、EINTR 被信号所中断.
5、EAGAIN 此动作会令进程阻断, 但参数s 的soket 为补课阻断的.
6、ENOBUFS 系统的缓冲内存不足.
7、EINVAL 传给系统调用的参数不正确.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
ret = sendto(skfd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&daddr, sizeof(struct sockaddr_nl));
if (!ret) {
	perror("sendto error\n");
	close(skfd);
	exit(-1);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

接收来自内核的数据:

//包含头文件
#include <sys/types.h>
#include <sys/socket.h>

//函数定义
int recvfrom(int s, void *buf, int len, unsigned int flags, struct sockaddr *from,int *fromlen);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

**函数说明:**recv()用来接收远程主机经指定的socket 传来的数据, 并把数据存到由参数buf 指向的内存空间, 参数len 为可接收数据的最大长度. 参数flags 一般设0, 其他数值定义请参考recv(). 参数from 用来指定欲传送的网络地址, 结构sockaddr 请参考bind(). 参数fromlen 为sockaddr 的结构长度.

返回值:成功则返回接收到的字符数, 失败则返回-1, 错误原因存于errno 中.

错误代码:
EBADF 参数s 非合法的socket 处理代码
EFAULT 参数中有一指针指向无法存取的内存空间.
ENOTSOCK 参数s 为一文件描述词, 非socket.
EINTR 被信号所中断.
EAGAIN 此动作会令进程阻断, 但参数s 的socket 为不可阻断.
ENOBUFS 系统的缓冲内存不足
ENOMEM 核心内存不足
EINVAL 传给系统调用的参数不正确.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
//消息头+消息体
typedef struct _user_msg_info {
	struct nlmsghdr hdr;
	char  msg[MSG_LEN];
} user_msg_info;

//Receive netlink message from kernel.
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("recv form kernel error\n");
	close(skfd);
	exit(-1);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

二、Netlink内核部分

1、在驱动初始化阶段初始化一个socket用于与用户态数据交互。

struct netlink_kernel_cfg {
    unsigned int    groups;
    unsigned int    flags;
    void        (*input)(struct sk_buff *skb);-----------------------------------input回调函数
    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);
};

//内核接收数据的回调函数
struct netlink_kernel_cfg cfg = { 
	.input  = netlink_rcv_msg, /* set recv callback */
};

struct socket *nlsk = NULL;
/* Create netlink socket */
nlsk = (struct sock *)netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);
if (nlsk == NULL) {   
	printk("netlink_kernel_create error !\n");
	return -1; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

2、回调函数处理用户态发送过来的数据。

skb->len; 
@len: Length of actual data
//接收函数的参数为struct sk_buffer *skb;
nlh = nlmsg_hdr(skb);  //Get nlmsghdr from sk_buff.
umsg = NLMSG_DATA(nlh); //Get payload from nlmsghdr.
  • 1
  • 2
  • 3
  • 4
  • 5

3、向用户态发送数据,创建一个新的sk_buffer -> nlmsg_put()设置sk_buffer nltmsghdr -> 填充paylaod -> 往端口发送数据。

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

	int ret;

	//Create sk_buff using nlmsg_new().
	nl_skb = nlmsg_new(len, GFP_ATOMIC);
	if(!nl_skb)
	{
		printk("netlink alloc failure\n");
		return -1;
	}

	//Set up nlmsghdr.
	nlh = nlmsg_put(nl_skb, 0, 0, NETLINK_TEST, len, 0);
	if(nlh == NULL)
	{
		printk("nlmsg_put failaure \n");
		nlmsg_free(nl_skb);  //If nlmsg_put() failed, nlmsg_free() will free sk_buff.
		return -1;
	}

	//Copy pbuf to nlmsghdr payload.
	memcpy(nlmsg_data(nlh), pbuf, len);
	ret = netlink_unicast(nlsk, nl_skb, USER_PORT, MSG_DONTWAIT);

	return ret;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

内核的发送端需要构造sk_buffer。

参考文档:

netlink:https://www.cnblogs.com/arnoldlu/p/9532254.html

netlink详细文档:https://blog.csdn.net/stone8761/article/details/72780863

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/183419
推荐阅读
相关标签
  

闽ICP备14008679号