Netlink 使用简单教程

Netlink 使用简单教程

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

Netlink 分为用户态与核心态两部分。

本篇笔记包含最基本的操作,内核态只使用单播

用户态

用户态数据结构

sockaddr_nl

地址,实现中会使用 user process 的地址与 kernel process 的地址

// #include <linux/netlink.h>
struct sockaddr_nl {
     __kernel_sa_family_t    nl_family;  /* AF_NETLINK, 表明是Netlink socket*/
     unsigned short  nl_pad;     /* zero */
     __u32       nl_pid;     /* port id */
     __u32       nl_groups;  /* multicast groups mask */
};

nlmsghd

netlink 的消息头

// #include <linux/netlink.h>
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 */
};

在实现过程中,nlmsghd 作为消息头,随后紧跟的一片内存空间是 payload。

  • nlmsg_type : 消息状态,可选项:
// linux/netlink.h

#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 : 消息标志,可选项:
// linux/netlink.h

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

/* Modifiers to DELETE request */
#define NLM_F_NONREC	0x100	/* Do not delete recursively	*/

/* Flags for ACK message */
#define NLM_F_CAPPED	0x100	/* request was capped */
#define NLM_F_ACK_TLVS	0x200	/* extended ACK TVLs were included */

用户态函数

参见例程,这里记录函数原型定义头文件

// sys/socket.h
// socket 创建函数
int socket (int __domain, int __type, int __protocol);
// bind 
int bind (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len);
// 发送数据
ssize_t sendto (int __fd, const void *__buf, size_t __n,
		       int __flags, __CONST_SOCKADDR_ARG __addr,
		       socklen_t __addr_len);
// 接收数据
ssize_t recvfrom (int __fd, void *__restrict __buf, size_t __n,
			 int __flags, __SOCKADDR_ARG __addr,
			 socklen_t *__restrict __addr_len);

// unistd.h
// 关闭socket
int close (int __fd);

一些宏

NLMSG_SPACE(payload_size) // 计算发送 payload_size 所需要分配的空间大小
    // 空间大小包括 nlmsghd + payload
    // 空间大小是经过字节对齐的最小值,
NLMSG_DATA(nlh_ptr)	// 计算nlmsghdr指针对应的payload的开始指针
    
// 还有一些宏位于 linux/netlink.h

核心态

核心态需要使用 linux 内核编程,基础的内核编程内容不在本篇笔记范围内。

核心态使用与用户态相同的 nlmsghdr 结构体

核心态结构体

netlink_kernel_cfg

内核 netlink socket 配置参数结构体

/* 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; 
    void        (*bind)(int group); 
    bool        (*compare)(struct net *net, struct sock *sk);
};

netlink 协议类型

linux 有一些默认的 netlink 协议类型,相同协议类型的 socket 才能在用户态与核心态之间通信。

用户可以在未被使用的协议号中选择,最大值为32

// linux/netlink.h

#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

核心态函数

static inline struct sock *
netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg);
// net 网络命名空间,一般默认传入 &init_net
// unit netlink 协议类型
// cfg 存放配置参数,一般仅设置回调函数

/* 发送单播消息 */
extern int netlink_unicast(struct sock *ssk, struct sk_buff *skb, __u32 portid, int nonblock);
/*
 ssk: netlink socket 
 skb: skb buff 指针,可以通过skb获得nlmsghdr及后续的payload
 portid: 通信的端口号
 nonblock:表示该函数是否为非阻塞,如果为1,该函数将在没有接收缓存可利用时立即返回,而如果为0,该函数在没有接收缓存可利用定时睡眠
*/

实例

本实例是一个echo服务器

核心态

test_netlink.c

#include <linux/module.h>

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

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fg");
MODULE_DESCRIPTION("netlink try");
MODULE_VERSION("0.0");

#define NETLINK_TEST 30
#define PAYLOAD_MAX_SIZE    1024

struct sock *netlink_socket = NULL;
pid_t pid = -1;     

struct test_nlmsg {
    struct nlmsghdr nlh;
    u8    msg_data[PAYLOAD_MAX_SIZE];
};


// 收到信息的回调函数
static void netlink_message_handle(struct sk_buff *skb)
{
    // get test_nlmsg
    struct test_nlmsg *msg = (struct test_nlmsg *) skb->data;
    // set user process process pid
    pid = msg->nlh.nlmsg_pid;
    // 收到信息的 nlmsghdr 位于 msg->nlh
    // 收到信息的 payload 位于 msg->msg_data
    
    printk("Netlink info get!\n");

    // 构建发送信息结构体
    struct sk_buff *skb_out = nlmsg_new(PAYLOAD_MAX_SIZE, GFP_KERNEL);	// 使用该函数分配的内存空间会在 nlmsg_unicast 后自动释放
    struct nlmsghdr *nlh = nlmsg_put(skb_out, 0, 0, NLMSG_DONE, PAYLOAD_MAX_SIZE, 0);
    NETLINK_CB(skb_out).dst_group = 0;
    // fill echo data
    strcpy((char *)NLMSG_DATA(nlh), msg->msg_data);
	
    nlmsg_unicast(netlink_socket, skb_out, pid);
}


static int test_socket_create(void)
{
    // set message receive callback function
    struct netlink_kernel_cfg cfg = {
        .input = netlink_message_handle,
    };
	
    // 创建 netlink socket
    netlink_socket = (struct sock *)netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);

    if(netlink_socket == NULL)
    {
        printk("Socket Create Failed!\n");
        return -1;
    }
    printk("Socket Create Succeed!\n");
    return 0;
}


static void test_socket_close(void)
{
    // release socket
    if (netlink_socket){
        netlink_kernel_release(netlink_socket);
        netlink_socket = NULL;
    }   
    printk("Socket Release Succeed!\n");

}

module_init(test_socket_create);
module_exit(test_socket_close);

用户态

test_user.c

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

#define NETLINK_TEST         30
#define PAYLOAD_MAX_SIZE    1024

struct test_nlmsg {
    struct nlmsghdr nlh;
    uint8_t    msg_data[PAYLOAD_MAX_SIZE];
};

int netlink_socket = -1;
struct sockaddr_nl  *user_addr = NULL;      // self address
struct sockaddr_nl  *kernel_addr = NULL;    // target address
struct test_nlmsg    *msg = NULL;            // message buffer

int main ()
{
    // Create netlink socket
    netlink_socket = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
    if (netlink_socket == -1)
    {
        perror("Socket create failed!\n");
        return -1;
    }
    
    // fill msg head info
    msg = (struct test_nlmsg *)malloc(sizeof(struct test_nlmsg));
    if(msg == NULL)
    {
        perror("msg malloc failed!\n");
        close (netlink_socket);
        return -1;
    }
    memset(msg, 0, sizeof(struct test_nlmsg));
    msg->nlh.nlmsg_len = sizeof(struct test_nlmsg);
    msg->nlh.nlmsg_pid = getpid();  // sender's pid
    msg->nlh.nlmsg_flags = 0;   // no special flag

    // fill sender's addr
    user_addr = (struct sockaddr_nl *)malloc(sizeof(struct sockaddr_nl));
    if(user_addr == NULL)
    {
        perror("user_addr malloc failed!\n");
        close (netlink_socket);
        free(msg);
        return -1;
    }
    memset(user_addr, 0, sizeof(struct sockaddr_nl));
    user_addr->nl_family = AF_NETLINK;   // Netlink socket
    user_addr->nl_pid = getpid();        // user space process pid
    user_addr->nl_groups = 0;            // no multicast

    // fill receive's addr
    kernel_addr = (struct sockaddr_nl *)malloc(sizeof(struct sockaddr_nl *));
    if(kernel_addr == NULL)
    {
        perror("kernel_addr malloc failed!\n");
        close (netlink_socket);
        free(msg);
    	free(user_addr);
        return -1;
    }
    memset(kernel_addr, 0, sizeof(struct sockaddr_nl));
    kernel_addr->nl_family = AF_NETLINK;
    kernel_addr->nl_pid = 0;     // kernel process pid
    kernel_addr->nl_groups = 0;

    // bind socket
    int ret = bind(netlink_socket, (struct sockaddr *) user_addr, sizeof(struct sockaddr_nl));
    if (ret == -1)
    {
        perror("bind failed!\n");
        close (netlink_socket);
        free(msg);
    	free(user_addr);
	    free(kernel_addr);
        return -1;
    }
    
    // fill msg
    char *buf = "hello netlink!";
    memset(&(msg->msg_data), 0, PAYLOAD_MAX_SIZE);
    strcpy(msg->msg_data, buf);
    
    // send msg
    printf("Send message to kernel\n");
    ssize_t send_len = sendto(netlink_socket, msg, msg->nlh.nlmsg_len, 0, (struct sockaddr *)kernel_addr, sizeof(struct sockaddr_nl));
    if(send_len == -1)
    {
        perror("send failed!\n");
        close (netlink_socket);
        free(msg);
    	free(user_addr);
	    free(kernel_addr);
        return -1;
    }
    
    ssize_t recv_len = -1;
    struct test_nlmsg recv_msg;
    // recv msg
    socklen_t kernel_addrlen = sizeof(struct sockaddr_nl);
	recv_len = recvfrom(netlink_socket, &recv_msg, sizeof(struct test_nlmsg), 0, (struct sockaddr *)kernel_addr, &kernel_addrlen);
    if(recv_len == -1)
    {
        perror("recv failed!\n");
        free(msg);
    	free(user_addr);
	    free(kernel_addr);
        close (netlink_socket);
        return -1;
    }
    printf("Recv from kernel: %s\n", recv_msg.msg_data);
    
    // release
    close(netlink_socket);
    free(msg);
    free(user_addr);
    free(kernel_addr);
    
    return 0;
}

编译与运行

Makefile

obj-m:=test_netlink.o

all:
	make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules
	gcc $(PWD)/test_user.c -o test_user.out
clean:
	$(RM) -rf .*.cmd *.mod.c *.o *.ko .tmp* *.mod *.symvers *.order *.out

编译指令

$ make

安装内核模块

sudo insmod ./test_netlink.ko

运行

$ ./test_user.out

Send message to kernel
Recv from kernel: hello netlink!

参考

linux下netlink的使用简介 - 简书 (jianshu.com)

Implementing a New Custom Netlink Family Protocol | Better Tomorrow with Computer Science (insujang.github.io)

linux·c++·c
185 views
Comments
登录后评论
Sign In