Linux内核通信--Netlink

本文深入介绍了Netlink通信机制,一种用户态与内核态进程间通信的重要方式。Netlink不仅适用于路由、防火墙等网络应用,还支持多播、异步通信等功能。文章通过实例详细解析了Netlink的数据结构、API函数及其在用户态与内核态的具体应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、什么是Netlink通信机制

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


Netlink 是一种特殊的 socket,它是 Linux 所特有的,类似于 BSD 中的AF_ROUTE 但又远比它的功能强大,目前在Linux 内核中使用netlink 进行应用与内核通信的应用很多; 包括:路由 daemonNETLINK_ROUTE),用户态 socket 协议(NETLINK_USERSOCK),防火墙(NETLINK_FIREWALL),netfilter 子系统(NETLINK_NETFILTER),内核事件向用户态通知(NETLINK_KOBJECT_UEVENT),通用 netlinkNETLINK_GENERIC)等。


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


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


1netlink使用简单,只需要在include/linux/netlink.h中增加一个新类型的 netlink 协议定义即可,( #defineNETLINK_TEST 20 然后,内核和用户态应用就可以立即通过 socket API 使用该 netlink 协议类型进行数据交换);
2. netlink是一种异步通信机制,在内核与用户态应用之间传递的消息保存在socket缓存队列中,发送消息只是把消息保存在接收者的socket的接收队列,而不需要等待接收者收到消息;
3.使用 netlink 的内核部分可以采用模块的方式实现,使用 netlink 的应用部分和内核部分没有编译时依赖;
4netlink 支持多播,内核模块或应用可以把消息多播给一个netlink组,属于该neilink 组的任何内核模块或应用都能接收到该消息,内核事件向用户态的通知机制就使用了这一特性;
5.内核可以使用 netlink 首先发起会话;


二、Netlink常用数据结构及函数

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

下面简单介绍几种NETLINK用户态通信的常用数据结构


1、用户态数据结构

Netlink通信跟常用UDP Socket通信类似:
struct sockaddr_nl netlink通信地址跟普通socket struct sockaddr_in类似
struct sockaddr_nl结构:

struct sockaddr_nl {

     __kernel_sa_family_t    nl_family; /* AF_NETLINK (跟AF_INET对应)*/

     unsigned short  nl_pad;    /* zero */

     __u32      nl_pid;     /* port ID  (通信端口号)*/

     __u32      nl_groups;  /* multicast groupsmask */

};


struct nlmsghd 结构:

/* struct nlmsghd netlink消息头*/

 struct nlmsghdr {  

     __u32      nlmsg_len;  /* Length of messageincluding header */

     __u16      nlmsg_type; /* Message content */

    __u16      nlmsg_flags;    /* Additionalflags */

     __u32      nlmsg_seq;  /* Sequence number */

     __u32      nlmsg_pid;  /* Sending processport ID */

 };


1nlmsg_len:整个netlink消息的长度(包含消息头);
2nlmsg_type:消息状态,内核在include/uapi/linux/netlink.h中定义了以下4种通用的消息类型,它们分别是:

#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_NOOP:不执行任何动作,必须将该消息丢弃;

 NLMSG_ERROR:消息发生错误;

 NLMSG_DONE:标识分组消息的末尾;

 NLMSG_OVERRUN:缓冲区溢出,表示某些消息已经丢失。

 NLMSG_MIN_TYPEK:预留 */


3nlmsg_flags:消息标记,它们用以表示消息的类型,如下

  /* Flags values */

 

 #define NLM_F_REQUEST       1  /* It is request message.   */

 #define NLM_F_MULTI     2  /* Multipart message, terminated by NLMSG_DONE */

 #define NLM_F_ACK       4  /* Reply with ack, with zero or error code */

 #define NLM_F_ECHO      8  /* Echo this request        */

 #define NLM_F_DUMP_INTR     16 /* Dump was inconsistent due to sequence change */

 

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

 

4nlmsg_seq:消息序列号,用以将消息排队,有些类似TCP协议中的序号(不完全一样),但是netlink的这个字段是可选的,不强制使用;


5nlmsg_pid:发送端口的ID号,对于内核来说该值就是0,对于用户进程来说就是其socket所绑定的ID号。


struct msghdr 结构体

 

struct iovec {                    /* Scatter/gather arrayitems */

      void *iov_base;              /*Starting address */

      size_t iov_len;               /* Number of bytes to transfer*/

  };

   /* iov_base: iov_base指向数据包缓冲区,即参数buffiov_lenbuff的长度。msghdr中允许一次传递多个buff

     以数组的形式组织在 msg_iov中,msg_iovlen就记录数组的长度(即有多少个buff

   */

  struct msghdr {

      void         *msg_name;       /* optional address */

      socklen_t     msg_namelen;    /* size of address */

      struct iovec *msg_iov;        /* scatter/gather array */

      size_t        msg_iovlen;     /* # elements in msg_iov */

      void         *msg_control;    /* ancillary data, see below */

      size_t        msg_controllen; /* ancillary databuffer len */

      int           msg_flags;      /* flags on received message */

  };

  /* msg_name:数据的目的地址,网络包指向sockaddr_in, netlink则指向sockaddr_nl;

     msg_namelen: msg_name 所代表的地址长度

     msg_iov: 指向的是缓冲区数组

     msg_iovlen: 缓冲区数组长度

     msg_control: 辅助数据,控制信息(发送任何的控制信息)

     msg_controllen: 辅助信息长度

     msg_flags: 消息标识

  */


2. netlink 内核数据结构、常用宏及函数:

netlink消息类型:

#defineNETLINK_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_INET_DIAG   NETLINK_SOCK_DIAG

 

 #define MAX_LINKS 32

 

netlink常用宏

 #define NLMSG_ALIGNTO   4U

 /* NLMSG_ALIGN(len)用于得到不小于len且字节对齐的最小数值 */

 #define NLMSG_ALIGN(len) (((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )

 

 /* Netlink 头部长度 */

 #define NLMSG_HDRLEN     ((int) NLMSG_ALIGN(sizeof(structnlmsghdr)))

 

 /* 计算消息数据len的真实消息长度(消息体 + 消息头)*/

 #define NLMSG_LENGTH(len) ((len) +NLMSG_HDRLEN)

 

 /* NLMSG_SPACE(len)返回不小于NLMSG_LENGTH(len)且字节对齐的最小数值 */

 #define NLMSG_SPACE(len)NLMSG_ALIGN(NLMSG_LENGTH(len))

 

 /* NLMSG_DATA(nlh)用于取得消息的数据部分的首地址,设置和读取消息数据部分时需要使用该宏 */

 #define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))

 

 /* NLMSG_NEXT(nlh,len)用于得到下一个消息的首地址, 同时len 变为剩余消息的长度 */

 #define NLMSG_NEXT(nlh,len)  ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \

                   (structnlmsghdr*)(((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))

 

 /* NLMSG_PAYLOAD(nlh,len) 用于返回payload的长度*/

 #define NLMSG_PAYLOAD(nlh,len)((nlh)->nlmsg_len - NLMSG_SPACE((len)))

 

netlink 内核常用函数:

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

 static inline struct sock *

 netlink_kernel_create(struct net *net, intunit, struct netlink_kernel_cfg *cfg)

 /* net: net指向所在的网络命名空间, 一般默认传入的是&init_net(不需要定义);  定义在net_namespace.c(extern struct net init_net);

    unitnetlink协议类型

    cfg cfg存放的是netlink内核配置参数(如下)

 */

 /* optional Netlink kernel configurationparameters */

 struct netlink_kernel_cfg {

     unsigned int    groups; 

     unsigned int    flags; 

     void       (*input)(struct sk_buff *skb); /* input 回调函数 */

     struct mutex    *cb_mutex;

     void       (*bind)(int group);

     bool       (*compare)(struct net *net, struct sock *sk);

 };

 

单播netlink_unicast()  多播netlink_broadcast()

 

/* 来发送单播消息 */

 extern int netlink_unicast(struct sock *ssk, structsk_buff *skb, __u32 portid, int nonblock);

 /* ssk: netlink socket

    skb:skb buff 指针

   portid:通信的端口号

   nonblock:表示该函数是否为非阻塞,如果为1,该函数将在没有接收缓存可利用时立即返回,而如果为0,该函数在没有接收缓存可利用定时睡眠

 */

 

 /* 用来发送多播消息 */

 extern int netlink_broadcast(struct sock *ssk,struct sk_buff *skb, __u32 portid,

                  __u32 group, gfp_tallocation);

 /* ssk: 同上(对应netlink_kernel_create 返回值)、

    skb: 内核skb buff

   portid:端口id

   group: 是所有目标多播组对应掩码的"OR"操作的合值。

   allocation: 指定内核内存分配方式,通常GFP_ATOMIC用于中断上下文,而GFP_KERNEL用于其他场合。这个参数的存在是因为该API可能需要分配一个或多个缓冲区来对多播消息进行clone

 */

 

三、netlink实例


(1)用户态程序 (sendto(), recvfrom())

#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!!";

     /* 创建NETLINK socket */

     skfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);

     if(skfd == -1)

     {

         perror("create socketerror\n");

         return -1;

     }

     memset(&saddr, 0, sizeof(saddr));

     saddr.nl_family = AF_NETLINK; //AF_NETLINK

     saddr.nl_pid = 100;  //端口号(port ID)

     saddr.nl_groups = 0;

     if(bind(skfd, (struct sockaddr*)&saddr, sizeof(saddr)) != 0)

     {

         perror("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; //selfport

     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("sendto error\n");

         close(skfd);

         exit(-1);

     }

     printf("send kernel:%s\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("recv form kernelerror\n");

        close(skfd);

         exit(-1);

     }

     printf("from kernel:%s\n",u_info.msg);

     close(skfd);

     free((void *)nlh);

     return 0;

}

 

Netlink 内核模块代码:

#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

#define USER_PORT        100

 

MODULE_LICENSE("GPL");

MODULE_AUTHOR("zhangwj");

MODULE_DESCRIPTION("netlinkexample");

struct sock *nlsk = NULL;

extern struct netinit_net;

int send_usrmsg(char*pbuf, uint16_t len)

{

     struct sk_buff *nl_skb;

     struct nlmsghdr *nlh;

     int ret;

     /* 创建sk_buff 空间 */

     nl_skb = nlmsg_new(len, GFP_ATOMIC);

     if(!nl_skb)

     {

         printk("netlink allocfailure\n");

         return -1;

     }

     /* 设置netlink消息头部 */

     nlh = nlmsg_put(nl_skb, 0, 0,NETLINK_TEST, len, 0);

     if(nlh == NULL)

     {

         printk("nlmsg_put failaure\n");

         nlmsg_free(nl_skb);

         return -1;

     }

     /* 拷贝数据发送 */

     memcpy(nlmsg_data(nlh), pbuf, len);

     ret = netlink_unicast(nlsk, nl_skb,USER_PORT, MSG_DONTWAIT);

 

     return ret;

} 

static voidnetlink_rcv_msg(struct sk_buff *skb)

{

     struct nlmsghdr *nlh = NULL;

     char *umsg = NULL;

     char *kmsg = "hello users!!!";

     if(skb->len >= nlmsg_total_size(0))

     {

         nlh = nlmsg_hdr(skb);

         umsg = NLMSG_DATA(nlh);

         if(umsg)

         {

             printk("kernel recv fromuser: %s\n", umsg);

             send_usrmsg(kmsg, strlen(kmsg));

         }

     }

}

struct netlink_kernel_cfgcfg = {

         .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("netlink_kernel_createerror !\n");

         return -1;

     }  

     printk("test_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);

 


一、imp2源码 整个源码包含三个文件:imp2_k.c, imp2_u.c和imp2.h. 其中imp2_k.c为内核模块的源代码,imp2_u.c为应用程序,即测试代码,imp2.h为两个源文件都需要引用的头文件。其整体的功能是:注册一种新的netlink协议,并注册一个新的NF hook函数。当有ping包发往当前主机或者经过当前主机转发时,内核向用户发送ping包的源IP和目的IP。各个文件的简单分析见下文。 1. imp2.h 该文件主要是定义了一种新的Netlink协议类型NL_IMP2(31)。新的协议类型的选值不能和当前内核中已经定义的netlink协议类型重复。定义了基于该协议类型的消息类型,内核根据接收到消息的不同类型,进行不同的处理:IMP2_U_PID和IMP2_CLOSE分别为请求和关闭。IMP2_K_MSG代表内核空间发送的消息。 该头文件的源码如下: 2. imp2_k.c 该程序为内核模块程序。其完成的功能如下: (1)创建一种新的Netlink协议NL_IMP2,并注册该协议的回调函数kernel_receive。但用户空间通过建立且协议类型为NL_IMP2的socket套接字并调用sendto,sendmsg函数发送数据时,传送到内核空间的数据由kernel_receive进行处理。该函数主要是记录用户进程的ID,用于随后发送数据的时候指定目的。 (2)在Netfilter的hook点NF_IP_PRE_ROUTING注册hook函数get_icmp,对经过该hook点的ping包进行处理。get_icmp首先判断是否是ping包,如果不是,直接Accept。如果是,则记录该包的源IP和目的IP,然后调用send_to_user,将记录的信息发送给kernel_recieve函数中记录的用户进程ID。 该文件的源码如下: 3. imp2_u.c 该程序为用户空间的测试程序。该程序包括以下功能: (1)生成NL_IMP2协议的socket.然后通过调用sendto发送IMP2_U_PID类型的请求信息给内核。然后等待接受内核发回的信息。记住:仅当有ping包经过内核的NF时,内核才会向用户进程发送信息。 (2)当用户进程通过Ctrl+C来结束该程序时,调用信号处理函数sig_int,向内核发送IMP2_CLOSE的消息,结束socket。 该文件的源码如下: 二、编译和测试 1. 整个源文件编译的Makefile如下: all: gcc -O2 -DMODULE -D__KERNEL__ -W -Wstrict-prototypes -Wmissing-prototypes -isystem /lib/modules/`uname -r`/build/include -c -o imp2_k.o imp2_k.c gcc imp2_u.c -o imp2_u install: insmod imp2_k.o uninstall: rmmod imp2_k clean: rm -f imp2_k.o imp2_u 2. 加载内核模块,并执行测试程序。 #make install #./imp2_u 当没有ping包时,终端一直处于等待输出状态。通过另一台主机(192.168.1.100)向当前主机(192.168.1.101)发送ping包,则终端已经有输出: #./imp2_u [root@localhost imp2]# ./imp2_u src: 192.168.1.100, dest: 192.168.1.101 src: 192.168.1.100, dest: 192.168.1.101 src: 192.168.1.100, dest: 192.168.1.101 src: 192.168.1.100, dest: 192.168.1.101
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值