溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何進行Netlink源碼及實例的分析

發(fā)布時間:2022-01-07 15:45:26 來源:億速云 閱讀:103 作者:柒染 欄目:系統(tǒng)運維

本篇文章給大家分享的是有關如何進行Netlink源碼及實例的分析,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

前言

這幾天在看 ipvs 相關代碼的時候又遇到了 netlink 的事情,所以這兩天花了點時間重新把 netlink 的事情梳理了一下。

什么是 netlink

linux  內(nèi)核一直存在的一個嚴重問題就是內(nèi)核態(tài)和用戶態(tài)的交互的問題,對于這個問題內(nèi)核大佬們一直在研究各種方法,想讓內(nèi)核和用戶態(tài)交互能夠安全高效的進行。如系統(tǒng)調(diào)用,proc,sysfs等內(nèi)存文件系統(tǒng),但是這些方式一般都比較簡單,只能在用戶空間輪詢訪問內(nèi)核的變化,內(nèi)核的變化無法主動的推送出來。

而 netlink 的出現(xiàn)比較好的解決了這個問題,而且 netlink 還有以下一些優(yōu)勢:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術社區(qū)

  2. 可以直接使用 socket 套接字的 API 進行內(nèi)核和用戶態(tài)的通信,開發(fā)使用上相對簡單了很多。

  3. 利用內(nèi)核協(xié)議棧有了緩沖隊列,是一種異步通信機制。

  4. 可以是內(nèi)核和用戶態(tài)的雙向通信,內(nèi)核可以主動向用戶態(tài)進程發(fā)送消息。這個是以往通信方式不具備的。

  5. 針對同一個協(xié)議類型的所有用戶進程,內(nèi)核可以廣播消息給所有的進程,也可以指定進程 pid 進行消息發(fā)送。

目前 netlink 的這種機制被廣泛使用在各種場景中,在 Linux 內(nèi)核中使用 netlink 進行應用與內(nèi)核通信的應用很多; 包括:路由  daemon(NETLINK_ROUTE),用戶態(tài) socket  協(xié)議(NETLINK_USERSOCK),防火墻(NETLINK_FIREWALL),netfilter  子系統(tǒng)(NETLINK_NETFILTER),內(nèi)核事件向用戶態(tài)通知(NETLINK_KOBJECT_UEVENT)等。具體支持的類型可以查看這個文件  include/uapi/linux/netlink.h。

netlink 內(nèi)核代碼走讀

netlink 內(nèi)核相關文件介紹

netlink 的內(nèi)核代碼在內(nèi)核代碼的 net/netlink/ 目錄下,我目前看的是 5.7.10 的內(nèi)核版本,netlink  內(nèi)核相關的文件不多,還是比較清晰的:

helightxu@  ~/open_code/linux-5.7.10  ls net/netlink config      Makefile     af_netlink.c af_netlink.h diag.c       genetlink.c helightxu@  ~/open_code/linux-5.7.10 
文件描述
af_netlink.c 和 af_netlink.h:是 netlink 的核心文件,這個也是下面詳細走讀的內(nèi)容。
diag.c對 netlink sock 進行監(jiān)控,可以插入到內(nèi)核或者從內(nèi)核中卸載
genetlink.c這個可以看作是 netlink 的升級版本,或者說是一種高層封裝。

注:

genetlink.c 額外說明:netlink 默認支持了 30 多種的場景,但是對于其它場景并沒有具體定義,這個時候這種通用封裝就有了很大的好處,可以在不改動內(nèi)核的前提下進行應用場景擴展,這部分內(nèi)容可以看這個 wiki:https://wiki.linuxfoundation.org/networking/generic_netlink_howto

還有一個頭文件是在 include  目錄,如下所示,這個頭文件是一些輔助函數(shù)、宏定義和相關數(shù)據(jù)結構,大家學習的同學一定要看這個文件,它里面的注釋非常詳細。這些注釋對理解 netlink  的消息結構非常有用,建議可以詳細看看。

helightxu@  ~/open_code/linux-5.7.10  ls include/net/netlink.h

af_netlink.c 代碼走讀

在 af_netlink.c 這個文件的最下面有一行代碼:

core_initcall(netlink_proto_init);

這段代碼的意思是什么呢?通過看這個代碼最終的實現(xiàn)可以看出,就是告訴編譯器把 netlink_proto_init 這個函數(shù)放入到最終編譯出來二進制文件的  .init 段中,內(nèi)核在啟動的時候會從這個端里面的函數(shù)挨個的執(zhí)行。這里也就是說 netlink  是內(nèi)核默認就直接支持的,是原生內(nèi)核的一部分(這里其實想和內(nèi)核的動態(tài)插拔模塊區(qū)別)。

在 netlink_proto_init 函數(shù)中最關鍵的一行代碼就是下面最后一行,把 netlink 的協(xié)議族注冊到網(wǎng)絡協(xié)議棧中去。

static const struct net_proto_family netlink_family_ops = {     .family = PF_NETLINK,     .create = netlink_create,     .owner  = THIS_MODULE,  /* for consistency 8) */ }; ... sock_register(&netlink_family_ops);

PF_NETLINK 是表示 netlink 的協(xié)議族,在后面我們在客戶端創(chuàng)建 netlink socket  的時候就要用到這個東東。如下面的代碼,代碼來自我的測試代碼  https://github.com/helight/kernel_modules/tree/master/netlink_test  中的客戶端代碼,可以看出:PF_NETLINK 表示我們所用的就是 netlink 的協(xié)議,SOCK_RAW  表示我們使用的是原始協(xié)議包,NETLINK_USER 這個我們自己定義的一個協(xié)議字段。netlink 我們前面說了有 30  多種應用場景,這些都已經(jīng)在內(nèi)核代碼中固定了,所以在客戶端使用的時候會指定這個字段來表示和內(nèi)核中的那個應用場景的函數(shù)模塊進行交互。

//int socket(int domain, int type, int protocol);     sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_USER);

sock_register 這個函數(shù)的作用主要就是注冊 PF_NETLINK 這個協(xié)議類型到內(nèi)核中,讓內(nèi)核認識這個協(xié)議,在內(nèi)核網(wǎng)絡協(xié)議中建立 socket  的時候知道該使用那個協(xié)議為它提供操作支持。

注冊了之后內(nèi)核就支持了 netlink 協(xié)議了,接下來就是內(nèi)核中創(chuàng)建監(jiān)聽 socket,用戶態(tài)創(chuàng)建鏈接 socket 了。

netlink 用戶態(tài)和內(nèi)核交互過程

這里我簡單畫一個圖來表示一下,socket 通信主要有 2 個操作對象:server 端和 client 端。netlink 的操作原理是這樣的:

對象所處位置-
server 端內(nèi)核中-
client 端用戶態(tài)進程-

netlink 關鍵數(shù)據(jù)結構和函數(shù)

sockaddr_nl 協(xié)議套接字

netlink 的地址表示由 sockaddr_nl 負責

struct sockaddr_nl {     __kernel_sa_family_t    nl_family;    /* AF_NETLINK    */     unsigned short          nl_pad;        /* zero        */     __u32                   nl_pid;        /* port ID 這個一般是進程id */     __u32                   nl_groups;    /* multicast groups mask */ };

nl_family 制定了協(xié)議族,netlink 有自己獨立的值:AF_NETLINK,nl_pid 一般取為進程 pid。nl_groups  用以多播,當不需要多播時,該字段為 0。

nlmsghdr 消息體

netlink 消息是作為套接字緩沖區(qū) sk_buff 的數(shù)據(jù)部分傳遞的,其消息本身又分為頭部和數(shù)據(jù)。頭部為:

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

nlmsg_len 為消息的長度,包含該頭部在內(nèi)。nlmsg_pid 為發(fā)送進程的端口 ID,這個用戶可以自定義,一般也是使用進程 pid。

msghdr 用戶態(tài)系發(fā)送消息體

使用 sendmsg 和 recvmsg 函數(shù)進行發(fā)送和接收消息,使用的消息體是這個樣子的。

struct iovec {                    /* Scatter/gather array items */     void  *iov_base;              /* Starting address */     size_t iov_len;               /* Number of bytes to transfer */ }; /* iov_base: iov_base 指向數(shù)據(jù)包緩沖區(qū),即參數(shù) buff,iov_len 是 buff 的長度。 msghdr 中允許一次傳遞多個 buff,以數(shù)組的形式組織在 msg_iov 中,msg_iovlen 就記錄數(shù)組的長度 (即有多少個buff) */ struct msghdr {     void    *    msg_name;    /* Socket name            */     int          msg_namelen;    /* Length of name        */     struct iovec *    msg_iov;    /* Data blocks            */     __kernel_size_t   msg_iovlen;    /* Number of blocks        */     void     *         msg_control;    /* Per protocol magic (eg BSD file descriptor passing) */     __kernel_size_t    msg_controllen;    /* Length of cmsg list */     unsigned int      msg_flags; }; /*    msg_name:數(shù)據(jù)的目的地址,網(wǎng)絡包指向 sockaddr_in, netlink 則指向 sockaddr_nl;    msg_namelen: msg_name 所代表的地址長度    msg_iov: 指向的是緩沖區(qū)數(shù)組    msg_iovlen: 緩沖區(qū)數(shù)組長度    msg_control: 輔助數(shù)據(jù),控制信息(發(fā)送任何的控制信息)    msg_controllen: 輔助信息長度    msg_flags: 消息標識 */

邏輯結構如下:

如何進行Netlink源碼及實例的分析

socket 也是一種特殊的文件,通過 VFS 的接口同樣可以對其進行使用管理。socket  本身就需要實現(xiàn)文件系統(tǒng)的相應接口,有自己的操作方法集。

netlink 常用宏

#define NLMSG_ALIGNTO   4U/* 宏 NLMSG_ALIGN(len) 用于得到不小于len且字節(jié)對齊的最小數(shù)值 */ #define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) ) /* Netlink&nbsp;頭部長度 */ #define NLMSG_HDRLEN     ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr))) /* 計算消息數(shù)據(jù) len 的真實消息長度(消息體 + 消息頭)*/ #define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN) /* 宏 NLMSG_SPACE(len) 返回不小于 NLMSG_LENGTH(len) 且字節(jié)對齊的最小數(shù)值 */ #define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len)) /* 宏 NLMSG_DATA(nlh) 用于取得消息的數(shù)據(jù)部分的首地址,設置和讀取消息數(shù)據(jù)部分時需要使用該宏 */ #define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0))) /* 宏 NLMSG_NEXT(nlh,len) 用于得到下一個消息的首地址, 同時 len 變?yōu)槭S嘞⒌拈L度 */ #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)) /* NLMSG_PAYLOAD(nlh,len) 用于返回 payload 的長度*/ #define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))

netlink 內(nèi)核常用函數(shù)

netlink_kernel_create

這個內(nèi)核函數(shù)用于創(chuàng)建內(nèi)核 socket,以提供和用戶態(tài)通信

static inline struct sock * netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg) /*     net: 指向所在的網(wǎng)絡命名空間, 默認傳入的是 &init_net (不需要定義);  定義在 net_namespace.c(extern struct net init_net);     unit: netlink 協(xié)議類型     cfg:  cfg 存放的是 netlink 內(nèi)核配置參數(shù)(如下) */  /* optional Netlink kernel configuration parameters */ struct netlink_kernel_cfg {     unsigned int    groups;     unsigned int    flags;     void        (*input)(struct sk_buff *skb); /* input 回調(diào)函數(shù) */     struct mutex    *cb_mutex;     void        (*bind)(int group);     bool        (*compare)(struct net *net, struct sock *sk); };

單播函數(shù) netlink_unicast() 和多播函數(shù) netlink_broadcast()

/* 來發(fā)送單播消息 */ extern int netlink_unicast(struct sock *ssk, struct sk_buff *skb, __u32 portid, int nonblock); /* ssk: netlink socket    skb: skb buff 指針    portid:通信的端口號    nonblock:表示該函數(shù)是否為非阻塞,如果為1,該函數(shù)將在沒有接收緩存可利用時立即返回,而如果為 0,該函數(shù)在沒有接收緩存可利用 定時睡眠 */  /* 用來發(fā)送多播消息 */ extern int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, __u32 portid,                  __u32 group, gfp_t allocation); /* ssk: 同上(對應 netlink_kernel_create 返回值)、    skb: 內(nèi)核 skb buff    portid:端口id    group: 是所有目標多播組對應掩碼的"OR"操作的。    allocation: 指定內(nèi)核內(nèi)存分配方式,通常 GFP_ATOMIC 用于中斷上下文,而 GFP_KERNEL 用于其他場合。                 這個參數(shù)的存在是因為該 API 可能需要分配一個或多個緩沖區(qū)來對多播消息進行 clone。 */

測試例子代碼

netlink 內(nèi)核建立 socket 過程

內(nèi)核的代碼非常簡單,這里給出了核心代碼,就這么多,接收函數(shù)中直接打印了接收到的消息。

#include <linux/module.h>#include <linux/kernel.h>#include <linux/init.h>#include <net/sock.h>#include <asm/types.h>#include <linux/netlink.h>#include <linux/skbuff.h> #define NETLINK_XUX           31       /* testing */   static struct sock *xux_sock = NULL;  // 接收消息的回調(diào)函數(shù),接收參數(shù)是 sk_buff static void recv_netlink(struct sk_buff *skb) {     struct nlmsghdr *nlh;     nlh = nlmsg_hdr(skb); // 取得消息體     printk("receive data from user process: %s", (char *)NLMSG_DATA(nlh)); // 打印接收的數(shù)據(jù)內(nèi)容      ... }  int __init init_link(void) {     struct netlink_kernel_cfg cfg = {         .input = recv_netlink,     };     xux_sock = netlink_kernel_create(&init_net, NETLINK_XUX, &cfg); // 創(chuàng)建內(nèi)核 socket     if (!xux_sock){         printk("cannot initialize netlink socket");         return -1;     }          printk("Init OK!\n");     return 0; }

netlink 用戶態(tài)建立鏈接和收發(fā)信息

... // 上面的就省了 #define NETLINK_USER 31  //self defined #define MAX_PAYLOAD 1024 /* maximum payload size*/ struct sockaddr_nl src_addr, dest_addr; struct nlmsghdr *nlh = NULL; struct msghdr msg; struct iovec iov; int sock_fd;  int main(int args, char *argv[]) {     sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_USER); // 建立 socket      if(sock_fd < 0)         return -1;      memset(&src_addr, 0, sizeof(src_addr));     src_addr.nl_family = AF_NETLINK;     src_addr.nl_pid = getpid(); /* 當前進程的 pid */      if(bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr))){ // 和指定協(xié)議進行 socket 綁定         perror("bind() error\n");         close(skfd);         return -1;     }      memset(&dest_addr, 0, sizeof(dest_addr));     dest_addr.nl_family = AF_NETLINK;     dest_addr.nl_pid = 0;       /* For Linux Kernel */     dest_addr.nl_groups = 0;    /* unicast */      nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));     memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));     nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);     nlh->nlmsg_pid = getpid();  //self pid     nlh->nlmsg_flags = 0;     // 拷貝信息到發(fā)送緩沖中     strcpy(NLMSG_DATA(nlh), "Hello this is a msg from userspace");     // 構造發(fā)送消息體     iov.iov_base = (void *)nlh;         //iov -> nlh     iov.iov_len = nlh->nlmsg_len;     msg.msg_name = (void *)&dest_addr;     msg.msg_namelen = sizeof(dest_addr);     msg.msg_iov = &iov;  // iov 中存放 netlink 消息頭和消息數(shù)據(jù)     msg.msg_iovlen = 1;      printf("Sending message to kernel\n");      int ret = sendmsg(sock_fd, &msg, 0);  // 發(fā)送消息到內(nèi)核     printf("send ret: %d\n", ret);      printf("Waiting for message from kernel\n");      /* 從內(nèi)核接收消息 */     recvmsg(sock_fd, &msg, 0);     printf("Received message payload: %s\n", NLMSG_DATA(nlh));  // 打印接收到的消息      close(sock_fd);     return 0; }

netlink 目前感覺還是一個比較好用的內(nèi)核和用戶空間的交互方式,但是也是有他的使用場景,適合用戶空間和內(nèi)核空間主動交互的場景。

但是在單機場景下,大多數(shù)的主動權在用戶進程,用戶進程寫數(shù)據(jù)到內(nèi)核,用戶進程主動讀取內(nèi)核數(shù)據(jù)。這兩種場景覆蓋了內(nèi)核的絕大多數(shù)場景。

在內(nèi)核要主動的場景下,netlink 就比較適合。我能想到的就是內(nèi)核數(shù)據(jù)審計,安全觸發(fā)等,這類場景下內(nèi)核可以實時的告知用戶進程內(nèi)核發(fā)生的情況。

我是在看 ipvs 的代碼時候看到了里面有 netlink 的使用,發(fā)現(xiàn)早期 iptables 就是使用 netlink 來下發(fā)配置指令的,內(nèi)核中  netfilter 和 iptables 中還有這部分的代碼,今天也順便下載大致走讀了一遍,大家可以搜索 NETLINK 這個關鍵字來看。但是 iptables  后來的代碼中沒有使用這樣的方式,而是采用了一個叫做 iptc 的庫,其核心思路還是使用 setsockops 的方式,最終還是  copy_from_user。不過這種方式對于 iptables 這種配置下發(fā)的場景來說還是非常實用的。

以上就是如何進行Netlink源碼及實例的分析,小編相信有部分知識點可能是我們?nèi)粘9ぷ鲿姷交蛴玫降?。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI