• netlink原理及应用


    什么是netlink

    netlink是一种基于网络的通信机制,允许内核内部、内核与用户态应用之间甚至用户态应用之间进行通信;netlink的主要作用是内核与用户态之间通信;它的思想是,基于BSD的socket使用网络框架在内核和用户态之间进行通信;

    为什么要有netlink

    内核中有其他一些方法可以实现用户空间和内核通信,如procfs、sysfs和ioctrl等;netlink相比于这些方法,有以下优势:

    • 任何一方都不需要轮询;如果通过文件通信,用户态应用需要不断检查是否有新消息到达;
    • netlink使用简单,它是基于socket的,可以使用socket api;
    • 只需要在netlink协议族中新增加一个协议;使用netlink的内核部分可以采用模块的方式实现,之后使用socket api进行通信;
    • 内核可以直接向用户层发送信息,而无需用户层事先请求;
    • netlink支持单播、组播;内核模块可以把消息发送到一个多播组;

    数据结构

    struct sockaddr_nl

    netlink是基于网络的,使用socket通信;类似于其它网络协议,每个netlink socket都需要分配一个地址;struct sockaddr_nl表示netlink地址;

    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 */
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • nl_family,固定为AF_NETLINK,表示netlink协议族;

    netlink协议族包含多个协议,最大值32;理论上32以内未被占用的协议号,可以用于自定义netlink协议,但这种方法并不规范,对于未来更新内核版本兼容性不友好;更加合适的方法,是在generic netlink协议族中,添加子协议,如nl80211就是generic 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	
    
    • 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
    • nl_pid,socket的唯一标识符;对内核自身来说,该字段是0,而用户空间的应用程序通常使用其线程组ID;netlink并没有要求该字段是进程ID,它可以是任何值,只需要保证其唯一性;使用线程组ID不过是方便而已;nl_pid是一个单播地址;
    • nl_groups,多播组掩码,每个bit表示一个多播组;每个netlink协议族最多支持32个多播组;

    netlink内核核心函数

    netlink_kernel_create

    内核创建netlink socket;

    static inline struct sock *
    netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)
    {
    	return __netlink_kernel_create(net, unit, THIS_MODULE, cfg);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • net,表示网络命令空间;
    • uint,表示netlink子协议族,如:
    #define NETLINK_ROUTE		0	/* Routing/device hook				*/
    #define NETLINK_GENERIC		16
    
    • 1
    • 2
    • cfg,netlink kernel创建socket的可选参数;其中,input是该内核netlink模块收到消息后的处理函数;
    /* 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;
    	int		(*bind)(struct net *net, int group);
    	void		(*unbind)(struct net *net, int group);
    	bool		(*compare)(struct net *net, struct sock *sk);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    netlink消息格式

    netlink消息由两部分组成:消息头和消息体;消息头固定为16字节,消息体长度可变;
    image.png

    消息头

    消息头定义如下:

    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
    • nlmsg_len,整个消息的长度,包括消息头;
    • nlmsg_type,消息类型,netlink定义一下四种通用消息类型
    #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 */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • nlmsg_flags,消息标志;如NLM_F_REQUEST
    /* 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		*/
    
    /* Flags for ACK message */
    #define NLM_F_CAPPED	0x100	/* request was capped */
    #define NLM_F_ACK_TLVS	0x200	/* extended ACK TVLs were included */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • nlmsg_seq,消息序列号,表示一系列消息之间在时间上的前后关系;也可以通过request消息和ack消息使用相同的序列号,保证消息不丢失;
    • nlmsg_pid,消息发送者的port id;

    消息体

    netlink协议并没有严格要求消息体的格式,可以发送任意消息;但一般标准做法,消息体是用nlattr,即属性,采用tlv的形式;消息体组织形式如下:
    image.png

    struct nlattr定义如下:

    /*
     *  <------- NLA_HDRLEN ------> <-- NLA_ALIGN(payload)-->
     * +---------------------+- - -+- - - - - - - - - -+- - -+
     * |        Header       | Pad |     Payload       | Pad |
     * |   (struct nlattr)   | ing |                   | ing |
     * +---------------------+- - -+- - - - - - - - - -+- - -+
     *  <-------------- nlattr->nla_len -------------->
     */
    
    struct nlattr {
    	__u16           nla_len;
    	__u16           nla_type;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    netlink协议族组织形式

    netlink协议族、子协议族、子协议、命令,组织结构如下:
    image.png

    如何新增netlink子协议族

    如何将自定义netlink协议加入到netlink协议族中,于NETLINK_GENERIC同一级别?只需定义一个netlink协议号即可,由于netlink对消息体格式不做强制要求,可以传输简单的字符串;实际使用中,不建议这样做,但作为学习,可以简单的这样操作;实际使用中增加自定义netlink协议,建议加入到NETLINK_GENERIC协议族中,类似nl80211这样;
    下面代码,是直接在netlink中直接加入新的协议,定义协议号为30;内核中新增一个模块,处理该协议的消息;应用程序通过该协议,和内核通信;简单起见,直接传输字符串;应用程序先向内核发送一条消息,内核收到消息后进行回复;

    内核代码

    内核代码如下:

    
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define NETLINK_TEST     30
    #define MSG_LEN            125
    
    MODULE_LICENSE("GPL");
    
    struct sock *nlsk = NULL;
    extern struct net init_net;
    
    int send_usrmsg(char *pbuf, uint16_t len, uint32_t pid)
    {
        struct sk_buff *nl_skb;
        struct nlmsghdr *nlh;
    
        int ret;
    
        /* Allocate a new netlink message */
        nl_skb = nlmsg_new(len + 1, GFP_ATOMIC);
        if(!nl_skb)
        {
            printk("\nError:netlink alloc failure.\n\n");
            return -1;
        }
    
        /* Add a new netlink message to an skb
            pid是0,说明是从内核发送的
        */
        nlh = nlmsg_put(nl_skb, 0, 0, NETLINK_TEST, len, 0);
        if(nlh == NULL)
        {
            printk("\nError:nlmsg_put failaure. \n\n");
            nlmsg_free(nl_skb);
            return -1;
        }
    
        /* copy payload */
        memcpy(nlmsg_data(nlh), pbuf, len);
        ret = netlink_unicast(nlsk, nl_skb, pid, MSG_DONTWAIT);
    
        return ret;
    }
    
    static void netlink_rcv_msg(struct sk_buff *skb)
    {
        struct nlmsghdr *nlh = NULL;
        char *umsg = NULL;
        char *kmsg = "Hello user's program.";
    
        if(skb->len >= nlmsg_total_size(0))
        {
            nlh = nlmsg_hdr(skb);
            umsg = NLMSG_DATA(nlh);
            if(umsg)
            {
                printk("kernel recv from user space: %s\n", umsg);
                send_usrmsg(kmsg, strlen(kmsg), nlh->nlmsg_pid);
            }
        }
    }
    
    struct netlink_kernel_cfg cfg = {
            .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("\nError:netlink_kernel_create error !\n");
            return -1;
        }
        printk("\ntest_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);
    
    • 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
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    #
    #Desgin of Netlink
    #
    MODULE_NAME :=nl_test_kernel
    obj-m:=$(MODULE_NAME).o
    	
    
    KERNELDIR ?=/lib/modules/$(shell uname -r)/build
    PWD :=$(shell pwd)
    
    
    
    all:
    
    	$(MAKE) -C $(KERNELDIR) M=$(PWD)
    
    clean:
    
    	$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    nl_test_kernel.cMakefile放到同一目录下;直接make,编译生成nl_test_kernel.ko
    insmod nl_test_kernel.ko,将该模块加载到内核中;内核现在就可以处理NETLINK_TEST的消息了;
    image.png

    应用程序代码

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #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 protocol.";
    
        /* 创建NETLINK socket */
        skfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
        if(skfd == -1)
        {
            perror("\nError:Create socket error.\n");
            return -1;
        }
    
        memset(&saddr, 0, sizeof(saddr));
        saddr.nl_family = AF_NETLINK; //AF_NETLINK
        saddr.nl_pid = getpid();  //端口号(port ID)
        saddr.nl_groups = 0;
        if(bind(skfd, (struct sockaddr *)&saddr, sizeof(saddr)) != 0)
        {
            perror("\nError: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; //self port
    
        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("\nError:sendto error.\n");
            close(skfd);
            exit(-1);
        }
        printf("\nApplication-->Send to kernel:%s\n\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("\nError:recv form kernel error.\n");
            close(skfd);
            exit(-1);
        }
    
        printf("\nApplication-->From kernel:%s\n\n", u_info.msg);
        close(skfd);
    
        free((void *)nlh);
        return 0;
    }
    
    • 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
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

    gcc -o nl_test_user nl_test_user.c

    测试结果

    image.png

    如何新增自定义netlink协议

    如何在NETLINK_GENERIC中新增netlink协议?
    参考nl80211

    模块初始化时,通过genl_register_family注册通用netlink协议族,将命令以及处理函数进行注册;

    /* initialisation/exit functions */
    
    int __init nl80211_init(void)
    {
    	int err;
    
    	err = genl_register_family(&nl80211_fam);
    	if (err)
    		return err;
    
    	err = netlink_register_notifier(&nl80211_netlink_notifier);
    	if (err)
    		goto err_out;
    
    	return 0;
     err_out:
    	genl_unregister_family(&nl80211_fam);
    	return err;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    /**
     * genl_register_family - register a generic netlink family
     * @family: generic netlink family
     *
     * Registers the specified family after validating it first. Only one
     * family may be registered with the same family name or identifier.
     *
     * The family's ops, multicast groups and module pointer must already
     * be assigned.
     *
     * Return 0 on success or a negative error code.
     */
    int genl_register_family(struct genl_family *family)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    static const struct genl_ops nl80211_ops[] = {
    	{
    		.cmd = NL80211_CMD_GET_WIPHY,
    		.doit = nl80211_get_wiphy,
    		.dumpit = nl80211_dump_wiphy,
    		.done = nl80211_dump_wiphy_done,
    		.policy = nl80211_policy,
    		/* can be retrieved by unprivileged users */
    		.internal_flags = NL80211_FLAG_NEED_WIPHY |
    				  NL80211_FLAG_NEED_RTNL,
    	},
    	{
    		.cmd = NL80211_CMD_SET_WIPHY,
    		.doit = nl80211_set_wiphy,
    		.policy = nl80211_policy,
    		.flags = GENL_UNS_ADMIN_PERM,
    		.internal_flags = NL80211_FLAG_NEED_RTNL,
    	},
        ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
  • 相关阅读:
    Angular-Web前端框架
    ​力扣解法汇总1200-最小绝对差
    关于阿里java开发规范中枚举类型字段必须有注释的思考
    四川建筑模板厂家推荐:能强优品木业提供优质货源
    基于SSM的电动车上牌管理系统设计与实现
    Server2安装虚拟机
    Pytorch 分类网络训练方法(Resnet152为例)
    广度优先搜索简介
    设计模式——行为型模式
    VSCode编写OpenCV
  • 原文地址:https://blog.csdn.net/congchp/article/details/136390067