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!