Linux Netlink通信

本文详细介绍了Linux内核与用户态之间通信的Netlink机制,包括Netlink的优势、特点、用户态和内核态的数据结构、宏定义以及相关的API使用。Netlink支持全双工、异步通信,常用于内核与用户态进程间的双向通信,尤其是网络和设备驱动等场景。用户态通过socket API创建、绑定和发送Netlink消息,内核态则使用内核API创建Netlink socket并处理消息。
摘要由CSDN通过智能技术生成

前言

在编写ARM Linux下代码时发现应用层Socket无法实现抓包功能,例如应用层要把一些数据转发到另外一个网卡,这就需要使用到内核的Netlink技术。翻阅网上所有文档发现有些文档比较好,所以整理了一下。

参考文档一
参考文档二

什么是Netlink通信机制

Netlink是linux提供的用于内核和用户态进程之间的通信方式。

但是注意虽然Netlink主要用于用户空间和内核空间的通信,但是也能用于用户空间的两个进程通信。只是进程间通信有其他很多方式,一般不用Netlink。除非需要用到Netlink的广播特性时。

那么Netlink有什么优势呢?

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

Netlink协议基于BSD socket和AF_NETLINK地址簇(address family),使用32位的端口号寻址(以前称作PID),每个Netlink协议(或称作总线,man手册中则称之为netlink family),通常与一个或一组内核服务/组件相关联,如NETLINK_ROUTE用于获取和设置路由与链路信息、NETLINK_KOBJECT_UEVENT用于内核向用户空间的udev进程发送通知等。

netlink特点

  • 支持全双工、异步通信(当然同步也支持)
  • 用户空间可使用标准的BSD socket接口(但netlink并没有屏蔽掉协议包的构造与解析过程,推荐使用libnl等第三方库)
  • 在内核空间使用专用的内核API接口
  • 支持多播(因此支持“总线”式通信,可实现消息订阅)
  • 在内核端可用于进程上下文与中断上下文

用户态数据结构

详细传输结构体
下面详细介绍下各个结构体参数分别代表什么:

msghdr

去系统里找到指定的包含结构体的头文件,我的路径为/usr/include/x86_64-linux-gnu/sys/socket.h,如果不知道在哪里的可以通过find / -name socket.h -print命令将其打印出来.注意一定要和开发时包含的头文件一致<sys/socket.h>

struct msghdr {
	void         *msg_name;       /* 字段用于未连接的套接字,以指定数据报的目标地址(struct sockaddr_ln*) */
	socklen_t     msg_namelen;    /* 目标地址的长度 */
	struct iovec *msg_iov;        /* Scatter/gather array */
	size_t        msg_iovlen;     /* msg_iov的大小 */
	void         *msg_control;    /* recvmsg/sendmsg用的接收buffer */
	size_t        msg_controllen; /* msg_control的大小 */
	int           msg_flags;      /* msg_flags则是为了满足recv/send中flag的功能(unused) */
};

sockaddr_ln

struct sockaddr_nl {
	sa_family_t nl_family; /* 该字段总是为AF_NETLINK */
	unsigned short nl_pad; /* 目前未用到,填充为0 */
	pid_t nl_pid; /* 进程pid */
	u_32 nl_groups; /* 多播组掩码*/
};

nl_pid在Netlink规范里,PID全称是Port-ID(32bits),其主要作用是用于唯一的标识一个基于netlink的socket通道。通常情况下nl_pid都设置为当前进程的进程号。前面我们也说过,Netlink不仅可以实现用户-内核空间的通信还可使现实用户空间两个进程之间,或内核空间两个进程之间的通信。
该属性为0时一般指内核.
bind时使用

iovec


struct iovec {
    ptr_t iov_base; /* 指向一个缓冲区,这个缓冲区是存放的是readv所接收的数据或是writev将要发送的数据 */
    size_t iov_len; /* 长度 */
};

nlmsghdr

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

nlmsg_len整个消息的长度,按字节计算。包括了Netlink消息头本身。
nlmsg_type消息的类型,即是数据还是控制消息。目前(内核版本2.6.21)Netlink仅支持四种类型的控制消息,如下:

  • NLMSG_NOOP,空消息,什么也不做;
  • NLMSG_ERROR,指明该消息中包含一个错误;
  • NLMSG_DONE,如果内核通过Netlink队列返回了多个消息,那么队列的最后一条消息的类型为
  • NLMSG_DONE,其余所有消息的nlmsg_flags属性都被设置NLM_F_MULTI位有效。
  • NLMSG_OVERRUN,暂时没用到。

nlmsg_flags附加在消息上的额外说明信息,如上面提到的NLM_F_MULTI。

struct net {
    refcount_t        passive;    /* To decided when the network
                         * namespace should be freed.
                         */
    atomic_t        count;        /* To decided when the network
                         *  namespace should be shut down.
                         */
    spinlock_t        rules_mod_lock;

    atomic64_t        cookie_gen;
...
} __randomize_layout;

struct sock {
    /*
     * Now struct inet_timewait_sock also uses sock_common, so please just
     * don't add nothing before this first member (__sk_common) --acme
     */
    struct sock_common    __sk_common;
    socket_lock_t        sk_lock;
    atomic_t        sk_drops;
    int            sk_rcvlowat;
    struct sk_buff_head    sk_error_queue;
    struct sk_buff_head    sk_receive_queue;
...
};

struct sk_buff {
...
    struct sock        *sk;
...
    /* private: */
    __u32            headers_end[0];
    /* public: */

    /* These elements must be at the end, see alloc_skb() for details.  */
    sk_buff_data_t        tail;
    sk_buff_data_t        end;
    unsigned char        *head,
                *data;
    unsigned int        truesize;
    refcount_t        users;
};

struct nlmsghdr {
    __u32        nlmsg_len;    /* Length of message including header */----------整个netlink消息的长度,包含消息头。
    __u16        nlmsg_type;    /* Message content */----------------------------消息状态,内核在include/uapi/linux/netlink.h中定义以下4种通用的消息类型。
    __u16        nlmsg_flags;    /* Additional flags */--------------------------消息标记,它们泳衣表示消息的类型。
    __u32        nlmsg_seq;    /* Sequence number */-----------------------------消息序列号,用以将消息排队有些类似TCP协议中的序号(不完全一样),但是netlink的这个字段是可选的,不强制使用。
    __u32        nlmsg_pid;    /* Sending process port ID */---------------------发送端口的ID号,对于内核来说该值就是0,对于用户进程来说就是其socket所绑定的ID号。
};

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

宏定义相关

nlmsg_type和nlmsg_flags宏对应解释

nlmsg_type:

#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:

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

系统预定义了一系列netlink类型

#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     

netlink常用宏

#define NLMSG_ALIGNTO    4U
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )------------------用于得到不小于len且字节对齐的最小数值
#define NLMSG_HDRLEN     ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))----------------------netlink头部长度
#define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)-------------------------------------------计算消息数据len的真实消息长度,消息体+消息头
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))------------------------------------返回不小于NLMSG_LENGTH(len)且字节对齐的最小数值
#define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))-------------------------用于取得消息的数据部分的首地址,设置和读取消息数据部分时需要使用该宏
#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))--------------------------------------------------判断消息是否>len
#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))---------------------用于返回payload的长度

用户空间Netlink socket API

创建socket

int socket(int domain, int type, int protocol);

domain指代地址族,即AF_NETLINK
type套接字类型,即为SOCK_RAW或SOCK_DGRAM,因为netlink是一个面向数据报的服务;
protocol选择该套接字使用哪种netlink特征。
以下是几种预定义的协议类型:

  • NETLINK_ROUTE
  • NETLINK_FIREWALL
  • NETLINK_APRD
  • NETLINK_ROUTE6_FW

可以非常容易的添加自己的netlink协议。
为每一个协议类型最多可以定义32个多播组。
每一个多播组用一个bitmask来表示,1<<i(0<=i<= 31),这在一组进程和内核进程协同完成一项任务时非常有用。发送多播
netlink消息可以减少系统调用的数量,同时减少用来维护多播组成员信息的负担。

地址绑定bind

bind(fd, (struct sockaddr*)&, nladdr, sizeof(nladdr));

发送netlink消息

为了发送一条netlink消息到内核或者其他的用户空间进程,另外一个struct sockaddr_nl nladdr需要作为目的地址,这和使用sendmsg()发送一个UDP包是一样的。

如果该消息是发送至内核的,那么nl_pidnl_groups都置为0.

如果消息是发送给另一个进程的单播消息,nl_pid是另外一个进程的pid值而nl_groups为0。

如果消息是发送给一个或多个多播组的多播消息,所有的目的多播组必须bitmask必须or起来从而形成nl_groups域。

sendmsg(fd, &, msg, 0);

接收netlink消息

一个接收程序必须分配一个足够大的内存用于保存Netlink消息头和消息负载。然后其填充struct msghdr msg,再使用标准的recvmsg()函数来接收netlink消息。

当消息被正确的接收之后,nlh应该指向刚刚接收到的Netlink消息的头。nladdr应该包含接收消息的目的地址,其中包括了消息发送者的pid和多播组。同时,宏NLMSG_DATA(nlh),定义在<netlink.h>中,返回一个指向Netlink消息负载的指针。调用close()关闭fd描述符所标识的socket。

recvmsg(fd, &, msg, 0);

内核空间Netlink socket API

创建Netlink socket

一种API

struct sock *netlink_kernel_create(struct net *net,
								int unit,unsigned int groups,
								void (*input)(struct sk_buff *skb),
								struct mutex *cb_mutex,struct module *module);

net是一个网络名字空间namespace,在不同的名字空间里面可以有自己的转发信息库,有自己的一套net_device等等。默认情况下都是使用init_net这个全局变量。
unit表示Netlink协议类型,如NETLINK_TESTNETLINK_SELINUX
groups多播地址。
input为内核模块定义的Netlink消息处理函数,当有消 息到达这个Netlink socket时,该input函数指针就会被引用,且只有此函数返回时,调用者的sendmsg才能返回。
cb_mutex为访问数据时的互斥信号量。
module 一般为THIS_MODULE

第二种API

static inline struct sock *
netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)

net指向所在的网络命名空间,一般默认传入的是&init_net,不需要定义;定义在net_namespace.c中。
unit Netlink协议类型。
cfgcfg存放的是netlink内核配置参数struct netlink_kernel_cfg数据结构。

发送单播消息 netlink_unicast

int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 pid, int nonblock);

ssk为函数netlink_kernel_create()返回的socket。
skb存放消息,它的data字段指向要发送的netlink消息结构,而 skb的控制块保存了消息的地址信息,宏NETLINK_CB(skb)就用于方便设置该控制块。
pid为接收此消息进程的pid,即目标地址,如果目标为组或内核,它设置为 0。
nonblock表示该函数是否为非阻塞,如果为1,该函数将在没有接收缓存可利用时立即返回;而如果为0,该函数在没有接收缓存可利用定时睡眠。

发送广播消息 netlink_broadcast

int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid, u32 group, gfp_t allocation)

前面的三个参数与netlink_unicast相同
group为接收消息的多播组,该参数的每一个位代表一个多播组,因此如果发送给多个多播组,就把该参数设置为多个多播组组ID的位或。
allocation为内核内存分配类型,一般地为GFP_ATOMICGFP_KERNELGFP_ATOMIC用于原子的上下文(即不可以睡眠),而GFP_KERNEL用于非原子上下文。

释放 netlink socket

sock_release();

or


void netlink_kernel_release(struct sock *sk)

sk释放netlink_kernel_create()创建的sock。

用户例程


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

#define MAX_PAYLOAD 1024 // maximum payload size
#define NETLINK_TEST 25 //自定义的协议

int main(int argc, char* argv[])
{
    int state;
    struct sockaddr_nl src_addr, dest_addr;
    struct nlmsghdr *nlh = NULL; //Netlink数据包头
    struct iovec iov;
    struct msghdr msg;
    int sock_fd, retval;
    int state_smg = 0;
    
    /* Create a socket */
    sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
    if(sock_fd == -1){
        printf("error getting socket: %s", strerror(errno));
        return -1;
    }
    
    /* To prepare binding */
    memset(&src_addr, 0, sizeof(src_addr));
    src_addr.nl_family = AF_NETLINK;
    src_addr.nl_pid = 100; //A:设置源端端口号
    src_addr.nl_groups = 0;
    
    //Bind
    retval = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));
    if(retval < 0){
        printf("bind failed: %s", strerror(errno));
        close(sock_fd);
        return -1;
    }
    
    /* To orepare create mssage */
    nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
    if(!nlh){
        printf("malloc nlmsghdr error!\n");
        close(sock_fd);
        return -1;
    }
    
    memset(&dest_addr,0,sizeof(dest_addr));
    dest_addr.nl_family = AF_NETLINK;
    dest_addr.nl_pid = 0; //B:设置目的端口号
    dest_addr.nl_groups = 0;
    nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
    nlh->nlmsg_pid = 100; //C:设置源端口
    nlh->nlmsg_flags = 0;
    strcpy(NLMSG_DATA(nlh),"Hello you!"); //设置消息体
    iov.iov_base = (void *)nlh;
    iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);
    
    /* Create mssage */
    memset(&msg, 0, sizeof(msg));
    msg.msg_name = (void *)&dest_addr;
    msg.msg_namelen = sizeof(dest_addr);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    
    /* send message */
    printf("state_smg\n");
    state_smg = sendmsg(sock_fd,&msg,0);
    if(state_smg == -1)
    {
        printf("get error sendmsg = %s\n",strerror(errno));
    }
    memset(nlh,0,NLMSG_SPACE(MAX_PAYLOAD));
    
    /* receive message */
    printf("waiting received!\n");
    while(1)
    {
        printf("In while recvmsg\n");
        state = recvmsg(sock_fd, &msg, 0);
        if(state<0)
        {
            printf("state<1");
        }
        printf("Received message: %s\n",(char *) NLMSG_DATA(nlh));
    }
    
    close(sock_fd);
    return 0;
}

上面程序首先向内核发送一条消息;“Hello you”,然后进入循环一直等待读取内核的回复,并将收到的回复打印出来。如果看上面程序感觉很吃力,那么应该首先复习一下UDP中使用sendmsg的用法,特别时struct msghdr的结构要清楚,这里再赘述。下面主要分析与UDP发送数据包的不同点:

  • socket地址结构不同,UDP为sockaddr_in,Netlink为struct sockaddr_nl;

  • 与UDP发送数据相比,Netlink多了一个消息头结构struct nlmsghdr需要我们构造。

注意代码注释中的A、B、C三处分别设置了pid。首先解释一下什么是pid,网上很多文章把这个字段说成是进程的pid,其实这完全是望文生义。这里的pid和进程pid没有什么关系,仅仅相当于UDP的port。对于UDP来说port和ip标示一个地址,那对我们的NETLINK_TEST协议(注意Netlink本身不是一个协议)来说,pid就唯一标示了一个地址。所以你如果用进程pid做为标示当然也是可以的。当然同样的pid对于NETLINK_TEST协议和内核定义的其他使用Netlink的协议是不冲突的(就像TCP的80端口和UDP的80端口)。

下面分析这三处设置pid分别有什么作用,首先A和B位置的比较好理解,这是在地址(sockaddr_nl)上进行的设置,就是相当于设置源地址和目的地址(其实是端口),只是注意B处设置pid为0,0就代表是内核,可以理解为内核专用的pid,那么用户进程就不能用0做为自己的pid吗?这个只能说如果你非要用也是可以的,只是会产生一些问题,后面在分析。接下来看为什么C处的消息头仍然需要设置pid呢?这里首先要知道一个前提:内核不会像UDP一样根据我们设置的原、目的地址为我们构造消息头,所以我们不在包头写入我们自己的地址(pid),那内核怎么知道是谁发来的报文呢?当然如果内核只是处理消息不需要回复进程的话舍不设置这个消息头pid都可以。

所以每个pid的设置功能不同:A处的设置是要设置发送者的源地址,有人会说既然源地址又不会自动填充到报文中,我们为什么还要设置这个,因为你还可能要接收回复啊。就像寄信,你连“门牌号”都没有,即使你在写信时候写上你的地址是100号,对方回信目的地址也是100号,但是邮局发现根本没有这个地址怎么可能把信送到你手里呢?所以A的主要作用是注册源地址,保证可以收到回复,如果不需要回复当然可以简单将pid设置为0;B处自然就是收信人的地址,pid为0代表内核的地址,假如有一个进程在101号上注册了地址,并调用了recvmsg,如果你将B处的pid设置为101,那数据包就发给了另一个进程,这就实现了使用Netlink进行进程间通信;C相当于你在信封上写的源地址,通常情况下这个应该和你的真实地址(A)处注册的源地址相同,当然你要是不想收到回信,又想恶搞一下或者有特殊需求,你可以写成其他进程注册的pid(比如101)。这和我们现实中寄信是一样的,你给你朋友写封情书,把写信人写成你的另一个好基友,然后后果你懂得……

好了,有了这个例子我们就大概知道用户态怎么使用Netlink了。

内核态程序范例

范例一

#include <linux/init.h>
#include <linux/module.h>
#include <linux/timer.h>
#include <linux/time.h>
#include <linux/types.h>
#include <net/sock.h>
#include <net/netlink.h>

#define NETLINK_TEST 25
#define MAX_MSGSIZE 1024

int stringlength(char *s);
int err;
struct sock *nl_sk = NULL;
int flag = 0;

//向用户态进程回发消息
void sendnlmsg(char *message, int pid)
{
    struct sk_buff *skb_1;
    struct nlmsghdr *nlh;
    int len = NLMSG_SPACE(MAX_MSGSIZE);
    
    int slen = 0;
    if(!message || !nl_sk)
    {
        return ;
    }
    
    printk(KERN_ERR "pid:%d\n",pid);
    skb_1 = alloc_skb(len,GFP_KERNEL);
    if(!skb_1)
    {
        printk(KERN_ERR "my_net_link:alloc_skb error\n");
    }
    slen = stringlength(message);
    nlh = nlmsg_put(skb_1,0,0,0,MAX_MSGSIZE,0);
    NETLINK_CB(skb_1).pid = 0;
    NETLINK_CB(skb_1).dst_group = 0;
    message[slen]= '\0';
    memcpy(NLMSG_DATA(nlh),message,slen+1);
    printk("my_net_link:send message '%s'.\n",(char *)NLMSG_DATA(nlh));
    netlink_unicast(nl_sk,skb_1,pid,MSG_DONTWAIT);
}

int stringlength(char *s)
{
    int slen = 0;
    for(; *s; s++)
    {
        slen++;
    }
    return slen;
}

//接收用户态发来的消息
void nl_data_ready(struct sk_buff *__skb)
{
    struct sk_buff *skb;
    struct nlmsghdr *nlh;
    char str[100];
    struct completion cmpl;
    
    printk("begin data_ready\n");
    
    int i=10;
    int pid;
    
    skb = skb_get (__skb);
    if(skb->len >= NLMSG_SPACE(0))
    {
        nlh = nlmsg_hdr(skb);
        memcpy(str, NLMSG_DATA(nlh), sizeof(str));
        printk("Message received:%s\n",str) ;
        pid = nlh->nlmsg_pid;
        while(i--)
        {
            //我们使用completion做延时,每3秒钟向用户态回发一个消息
            init_completion(&cmpl);
            wait_for_completion_timeout(&cmpl,3 * HZ);
            sendnlmsg("I am from kernel!",pid);
        }
        flag = 1;
        kfree_skb(skb);
    }
}

// Initialize netlink
int netlink_init(void)

{
    nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, 1,
                                  nl_data_ready, NULL, THIS_MODULE);
    
    if(!nl_sk){
        printk(KERN_ERR "my_net_link: create netlink socket error.\n");
        return 1;
    }
    
    printk("my_net_link_4: create netlink socket ok.\n");
    return 0;
}

static void netlink_exit(void)
{
    if(nl_sk != NULL){
        sock_release(nl_sk->sk_socket);
    }
    
    printk("my_net_link: self module exited\n");
}

module_init(netlink_init);

module_exit(netlink_exit);

MODULE_AUTHOR("chihao");

MODULE_LICENSE("GPL");

我们将内核模块insmod后,运行用户态程序,结果如下:
在这里插入图片描述
这个结果复合我们的预期,但是运行过程中打印出“state_smg”卡了好久才输出了后面的结果。这时候查看客户进程是处于D状态的(不了解D状态的同学可以google一下)。这是为什么呢?因为进程使用Netlink向内核发数据是同步,内核向进程发数据是异步。什么意思呢?也就是用户进程调用sendmsg发送消息后,内核会调用相应的接收函数,但是一定到这个接收函数执行完用户态的sendmsg才能够返回。我们在内核态的接收函数中调用了10次回发函数,每次都等待3秒钟,所以内核接收函数30秒后才返回,所以我们用户态程序的sendmsg也要等30秒后才返回。相反,内核回发的数据不用等待用户程序接收,这是因为内核所发的数据会暂时存放在一个队列中。
再来回到之前的一个问题,用户态程序的源地址(pid)可以用0吗?我把上面的用户程序的A和C处pid都改为了0,结果一运行就死机了。为什么呢?我们看一下内核代码的逻辑,收到用户消息后,根据消息中的pid发送回去,而pid为0,内核并不认为这是用户程序,认为是自身,所有又将回发的10个消息发给了自己(内核),这样就陷入了一个死循环,而用户态这时候进程一直处于D。

另外一个问题,如果同时启动两个用户进程会是什么情况?答案是再调用bind时出错:“Address already in use”,这个同UDP一样,同一个地址同一个port如果没有设置SO_REUSEADDR两次bind就会出错,之后我用同样的方式再Netlink的socket上设置了SO_REUSEADDR,但是并没有什么效果。

范例二

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

#define MAX_PAYLOAD 1024 // maximum payload size
#define NETLINK_TEST 25

int main(int argc, char* argv[])
{
    struct sockaddr_nl src_addr, dest_addr;
    struct nlmsghdr *nlh = NULL;
    int sock_fd, retval;
    int state,state_smg = 0;
    
    // Create a socket
    sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
    if(sock_fd == -1){
        printf("error getting socket: %s", strerror(errno));
        return -1;
    }
    
    // To prepare binding
    memset(&src_addr, 0, sizeof(src_addr));
    src_addr.nl_family = AF_NETLINK;
    src_addr.nl_pid = 100;
    src_addr.nl_groups = 0;
    
    //Bind
    retval = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));
    if(retval < 0){
        printf("bind failed: %s", strerror(errno));
        close(sock_fd);
        return -1;
    }
    
    // To orepare create mssage head
    nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
    if(!nlh){
        printf("malloc nlmsghdr error!\n");
        close(sock_fd);
        return -1;
    }
    
    memset(&dest_addr,0,sizeof(dest_addr));
    dest_addr.nl_family = AF_NETLINK;
    dest_addr.nl_pid = 0;
    dest_addr.nl_groups = 0;
    nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
    nlh->nlmsg_pid = 100;
    nlh->nlmsg_flags = 0;
    strcpy(NLMSG_DATA(nlh),"Hello you!");
    
    //send message
    printf("state_smg\n");
    sendto(sock_fd,nlh,NLMSG_LENGTH(MAX_PAYLOAD),0,(struct sockaddr*)(&dest_addr),sizeof(dest_addr));
    if(state_smg == -1)
    {
        printf("get error sendmsg = %s\n",strerror(errno));
    }
    memset(nlh,0,NLMSG_SPACE(MAX_PAYLOAD));
    
    //receive message
    printf("waiting received!\n");
    while(1){
        printf("In while recvmsg\n");
        state=recvfrom(sock_fd,nlh,NLMSG_LENGTH(MAX_PAYLOAD),0,NULL,NULL);
        if(state<0)
        {
            printf("state<1");
        }
        printf("Received message: %s\n",(char *) NLMSG_DATA(nlh));
        memset(nlh,0,NLMSG_SPACE(MAX_PAYLOAD));
    }
    
    close(sock_fd);
    return 0;
}

熟悉UDP编程的同学看到这个程序一定很熟悉,除了多了一个Netlink消息头的设置。但是我们发现程序中调用了bind函数,这个函数再UDP编程中的客户端不是必须的,因为我们不需要把UDP socket与某个地址关联,同时再发送UDP数据包时内核会为我们分配一个随即的端口。但是对于Netlink必须要有这一步bind,因为Netlink内核可不会为我们分配一个pid。
再强调一遍消息头(nlmsghdr)中的pid是告诉内核接收端要回复的地址,但是这个地址存不存在内核并不关心,这个地址只有用户端调用了bind后才存在。

我们看到这两个例子都是用户态首先发起的,那Netlink是否支持内核态主动发起的情况呢?
当然是可以的,只是内核一般需要事件触发,这里,只要和用户态约定号一个地址(pid),内核直接调用netlink_unicast就可以了。

内核编译Makefile

ifneq ($(KERNELRELEASE),)
	obj-m :=netl.o
else
	KERNELDIR ?=/lib/modules/$(shell uname -r)/build
	PWD :=$(shell pwd)
default:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值