Netlink 內核實現分析(三):通信實現


參考自:

http://blog.chinaunix.net/uid-28541347-id-5578403.html

https://blog.csdn.net/jasenwan88/article/details/7365060

https://www.cnblogs.com/oracleloyal/p/5991819.html

https://blog.csdn.net/kanda2012/article/details/7580623

https://blog.csdn.net/u012819339/article/details/51334600

一:Netlink簡介

(一)什么是Netlink?

Netlink是linux提供的用於內核和用戶態進程之間的通信方式。但是注意雖然Netlink主要用於用戶空間和內核空間的通信,但是也能用於用戶空間的兩個進程通信。只是進程間通信有其他很多方式,一般不用Netlink,除非需要用到Netlink的廣播特性時。

(二)那么Netlink有什么優勢呢?

一般來說用戶空間和內核空間的通信方式有三種:/proc、ioctl、Netlink。而前兩種都是單向的,但是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接口
 ④ 支持多播(因此支持“總線”式通信,可實現消息訂閱)
 ⑤ 在內核端可用於進程上下文與中斷上下文

(四)NETLINK對比UDP

AF_NETLINK和AF_INET對應,是一個協議族,而NETLINK_ROUTE、NETLINK_GENERIC這些是協議,對應於UDP。
那么我們主要關注Netlink和UDP socket之間的不同點,其中最重要的一點就是:

使用UDP socket發送數據包時,用戶無需構造UDP數據包的包頭,內核協議棧會根據原、目的地址(sockaddr_in)填充頭部信息。但是Netlink需要我們自己構造一個包頭(這個包頭有什么用,我們后面再說)。

一般我們使用Netlink都要指定一個協議,我們可以使用內核為我們預留的NETLINK_GENERIC(定義在linux/netlink.h中),也可以使用我們自定義的協議,其實就是定義一個內核還沒有占用的數字。下面我們用NETLINK_TEST做為我們定義的協議寫一個例子(注意:自定義協議不一定非要添加到linux/netlink.h中,只要用戶態和內核態代碼都能找到該定義就行)。我們知道使用UDP發送報文有兩種方式:sendto和sendmsg,同樣Netlink也支持這兩種方式。下面先看使用sendmsg的方式。

二:用戶態數據結構

首先看一下幾個重要的數據結構的關系:

 

(一)struct msghdr

msghdr這個結構在socket變成中就會用到,並不算Netlink專有的。

我們知道socket消息的發送和接收函數一般有這幾對:recv/send、readv/writev、recvfrom/sendto。當然還有recvmsg/sendmsg,前面三對函數各有各的特點功能,而recvmsg/sendmsg就是要囊括前面三對的所有功能,當然還有自己特殊的用途。msghdr的前兩個成員就是為了滿足recvfrom/sendto的功能,中間兩個成員msg_iov和msg_iovlen則是為了滿足readv/writev的功能,而最后的msg_flags則是為了滿足recv/send中flag的功能,剩下的msg_control和msg_controllen則是滿足recvmsg/sendmsg特有的功能。

struct user_msghdr {
    void        __user *msg_name;    /* ptr to socket address structure */
    int        msg_namelen;        /* size of socket address structure */
    struct iovec    __user *msg_iov;    /* scatter/gather array */
    __kernel_size_t    msg_iovlen;        /* # elements in msg_iov */
    void        __user *msg_control;    /* ancillary data */
    __kernel_size_t    msg_controllen;        /* ancillary data buffer length */
    unsigned int    msg_flags;        /* flags on received message */
};

應用層向內核傳遞消息可以使用sendto()或sendmsg()函數,其中sendmsg函數需要應用程序手動封裝msghdr消息結構,而sendto()函數則會由內核代為分配。其中
(1)msg_name:指向數據包的目的地址;
(2)msg_namelen:目的地址數據結構的長度;
(3)msg_iov:消息包的實際數據塊,定義如下:

struct iovec
{
  void *iov_base; /* BSD uses caddr_t (1003.1g requires void *) */
  __kernel_size_t iov_len; /* Must be size_t (1003.1g) */
};

iov_base:消息包實際載荷的首地址;
iov_len:消息實際載荷的長度。
(4)msg_control:消息的輔助數據;
(5)msg_controllen:消息輔助數據的大小;
(6)msg_flags:接收消息的標識。
對於該結構,我們更需要關注的是前三個變量參數,對於netlink數據包來說其中msg_name指向的就是目的sockaddr_nl地址結構實例的首地址,iov_base指向的就是消息實體中的nlmsghdr消息頭的地址,而iov_len賦值為nlmsghdr中的nlmsg_len即可(消息頭+實際數據)。

(二)struct sockaddr_nl

struct sockaddr_nl為Netlink的地址,和我們通常socket編程中的sockaddr_in作用一樣,他們的結構對比如下:

struct sockaddr_nl{}的詳細定義和描述如下:

    struct sockaddr_nl
    {
        sa_family_t nl_family; /*該字段總是為AF_NETLINK */
        unsigned short nl_pad; /* 目前未用到,填充為0*/
        __u32 nl_pid; /* process pid */
        __u32 nl_groups; /* multicast groups mask */
    };

  (1) nl_pid:在Netlink規范里,PID全稱是Port-ID(32bits),其主要作用是用於唯一的標識一個基於netlink的socket通道。通常情況下nl_pid都設置為當前進程的進程號。前面我們也說過,Netlink不僅可以實現用戶-內核空間的通信還可使現實用戶空間兩個進程之間,或內核空間兩個進程之間的通信。該屬性為0時一般指內核。
  (2) nl_groups:如果用戶空間的進程希望加入某個多播組,則必須執行bind()系統調用。該字段指明了調用者希望加入的多播組號的掩碼(注意不是組號,后面我們會詳細講解這個字段)。如果該字段為0則表示調用者不希望加入任何多播組。對於每個隸屬於Netlink協議域的協議,最多可支持32個多播組(因為nl_groups的長度為32比特),每個多播組用一個比特來表示。

(三)struct nlmsghdr

Netlink的報文由消息頭和消息體構成,struct nlmsghdr即為消息頭。消息頭定義在文件里,由結構體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 */
    };

消息頭中各成員屬性的解釋及說明:
 (1) nlmsg_len:整個消息的長度,按字節計算。包括了Netlink消息頭本身。
 (2) nlmsg_type:消息的類型,即是數據還是控制消息。目前(內核版本2.6.21)Netlink僅支持四種類型的控制消息,如下:
   a) NLMSG_NOOP-空消息,什么也不做;不執行任何動作,必須將該消息丟棄;
   b) NLMSG_ERROR-指明該消息中包含一個錯誤;
   c) NLMSG_DONE-標識分組消息的末尾;如果內核通過Netlink隊列返回了多個消息,那么隊列的最后一條消息的類型為NLMSG_DONE,其余所有消息的nlmsg_flags屬性都被設置NLM_F_MULTI位有效。
   d) NLMSG_OVERRUN-緩沖區溢出,表示某些消息已經丟失。
 (3) nlmsg_flags:附加在消息上的額外說明信息,如上面提到的NLM_F_MULTI。消息標記,它們用以表示消息的類型.

#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        */
(4)nlmsg_seq:消息序列號,用以將消息排隊,有些類似TCP協議中的序號(不完全一樣),但是netlink的這個字段是可選的,不強制使用;
(5)nlmsg_pid:發送端口的ID號,對於內核來說該值就是0,對於用戶進程來說就是其socket所綁定的ID號。

那消息體怎么設置呢?可以使用NLMSG_DATA,具體見后面例子。

(四)netlink消息處理宏

#define NLMSG_ALIGNTO    4U
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )            /* 對len執行4字節對齊 */
#define NLMSG_HDRLEN     ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))                /* netlink消息頭長度 */
#define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)                                    /* netlink消息載荷len加上消息頭 */
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))                                /* 對netlink消息全長執行字節對齊 */
#define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))                    /* 獲取netlink消息實際載荷位置 */
#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))                                            /* 驗證消息的長度 */
#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))                /* 返回PAYLOAD的長度 */

Linux為了處理netlink消息方便,在 include/uapi/linux/netlink.h中定義了以上消息處理宏,用於各種場合。對於Netlink消息來說,處理如下格式(見netlink.h):

/* ========================================================================
 *         Netlink Messages and Attributes Interface (As Seen On TV)
 * ------------------------------------------------------------------------
 *                          Messages Interface
 * ------------------------------------------------------------------------
 *
 * Message Format:
 *    <--- nlmsg_total_size(payload)  --->
 *    <-- nlmsg_msg_size(payload) ->
 *   +----------+- - -+-------------+- - -+-------- - -
 *   | nlmsghdr | Pad |   Payload   | Pad | nlmsghdr
 *   +----------+- - -+-------------+- - -+-------- - -
 *   nlmsg_data(nlh)---^                   ^
 *   nlmsg_next(nlh)-----------------------+
 *
 * Payload Format:
 *    <---------------------- nlmsg_len(nlh) --------------------->
 *    <------ hdrlen ------>       <- nlmsg_attrlen(nlh, hdrlen) ->
 *   +----------------------+- - -+--------------------------------+
 *   |     Family Header    | Pad |           Attributes           |
 *   +----------------------+- - -+--------------------------------+
 *   nlmsg_attrdata(nlh, hdrlen)---^
 *
 * ------------------------------------------------------------------------
 *                          Attributes Interface
 * ------------------------------------------------------------------------
 *
 * Attribute Format:
 *    <------- nla_total_size(payload) ------->
 *    <---- nla_attr_size(payload) ----->
 *   +----------+- - -+- - - - - - - - - +- - -+-------- - -
 *   |  Header  | Pad |     Payload      | Pad |  Header
 *   +----------+- - -+- - - - - - - - - +- - -+-------- - -
 *                     <- nla_len(nla) ->      ^
 *   nla_data(nla)----^                        |
 *   nla_next(nla)-----------------------------'
 *
 *=========================================================================
 */

三:內核態實現

(一)內核netlink 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);

   參數說明:

  (1) net:是一個網絡名字空間namespace,在不同的名字空間里面可以有自己的轉發信息庫,有自己的一套net_device等等。默認情況下都是使用 init_net這個全局變量。
  (2) unit:表示netlink協議類型,如NETLINK_TEST、NETLINK_SELINUX。
  (3) groups:多播地址。
  (4) input:為內核模塊定義的netlink消息處理函數,當有消 息到達這個netlink socket時,該input函數指針就會被引用,且只有此函數返回時,調用者的sendmsg才能返回。
  (5) cb_mutex:為訪問數據時的互斥信號量。
  (6) module: 一般為THIS_MODULE。

2.發送單播消息netlink_unicast

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

   參數說明:

  (1) ssk:為函數 netlink_kernel_create()返回的socket。
  (2) skb:存放消息,它的data字段指向要發送的netlink消息結構,而 skb的控制塊保存了消息的地址信息,宏NETLINK_CB(skb)就用於方便設置該控制塊。
  (3) pid:為接收此消息進程的pid,即目標地址,如果目標為組或內核,它設置為 0。
  (4) nonblock:表示該函數是否為非阻塞,如果為1,該函數將在沒有接收緩存可利用時立即返回;而如果為0,該函數在沒有接收緩存可利用定時睡眠。

3.發送廣播消息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_ATOMIC或GFP_KERNEL,GFP_ATOMIC用於原子的上下文(即不可以睡眠),而GFP_KERNEL用於非原子上下文。

void netlink_kernel_release(struct sock *sk)

(二)代碼實現

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <linux/netlink.h>
#include <net/sock.h>

#define NETLINK_USER 22
#define USER_MSG (NETLINK_USER+1)
#define USER_PORT 50

static int stringlen(char* buf);

static int send_msg(char* pbuf,int pid);
static void recv_cb(struct sk_buff* skb);

static struct sock* netlink_fd = NULL;

struct netlink_kernel_cfg cfg = {
    .input = recv_cb,
};

static int __init test_netlink_init(void)
{
    printk("init netlink!\n");

    netlink_fd = netlink_kernel_create(&init_net,USER_MSG,&cfg);
    if(!netlink_fd)
    {
        printk(KERN_ERR "can not create a netlink socket!\n");
        return -1;
    }

    printk("netlink init successful!\n");
    return 0;
}

static void __exit test_netlink_exit(void)
{
    printk("exit netlink!\n");
    sock_release(netlink_fd->sk_socket);
    printk(KERN_DEBUG "netlink exit!\n");
}

module_init(test_netlink_init);
module_exit(test_netlink_exit);

MODULE_AUTHOR("SSYFJ");
MODULE_LICENSE("GPL");

static void recv_cb(struct sk_buff* __skb)
{
    struct nlmsghdr* nlh = NULL;
    void* data = NULL;
    int pid;

    struct sk_buff* skb = skb_get(__skb);    //通過為原始數據__skb添加引用來獲取數據

    printk("skb->len:%u\n",skb->len);
    if(skb->len>=NLMSG_SPACE(0))             //NLMSG_SPACE(0)表示  只有頭部的大小
    {
        nlh = nlmsg_hdr(skb);
        printk("nlmsg->len:%u %u\n",nlh->nlmsg_len,nlmsg_len(nlh));
        data = NLMSG_DATA(nlh);
        if(data)
        {
            printk("kernel receive data:%s\n",(int8_t*)data);
            pid = nlh->nlmsg_pid;
            send_msg("hello userspace",pid);
        }
     }

     kfree_skb(skb);                            //前面引用+1,這里一定要釋放,不然會warn
}

static int stringlen(char* buf)
{
    int len = 0;
    for(;*buf;buf++)
    {
        len++;
    }
    return len;
}


static int send_msg(char* pbuf,int pid)
{
    int len = stringlen(pbuf),ret;
    
    struct sk_buff* nl_skb;
    struct nlmsghdr* nlh;

    nl_skb = nlmsg_new(len,GFP_ATOMIC);
    if(!nl_skb)
    {
        printk("netlink_alloc_skb error!\n");
        return -1;
    }

    nlh = nlmsg_put(nl_skb,0,0,pid,len,0);
    if(!nlh)
    {
        printk("nlmsg_put() error!\n");
        nlmsg_free(nl_skb);
        return -1;
    }

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

    ret = netlink_unicast(netlink_fd,nl_skb,pid,MSG_DONTWAIT);

    return ret;
}

(三)Makefile文件

obj-m := kern.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
    $(MAKE) -C $(KDIR) M=$(PWD) modules

注意:kern.o與我們編寫的代碼文件一致

(四)內核操作

sudo insmod kern.ko
sudo rmmod kern

其中kern是我們編譯出來的內核文件,對於ko文件的插入移除,可以通過dmesg查看printk的打印結果

四:用戶態實現

(一)sendmsg與recvmsg方法的用戶態實現

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/netlink.h>

#define NETLINK_USER 22                //從22開始到32可以自定義Netlink通信宏
#define USER_MSG (NETLINK_USER+1)      //自定義用於和內核空間通信

#define MAX_PLOAD 1024                 //nlmsg最大負載(包含頭部)


int main(int argc,char** argv)
{
    char* data = "hello kernel space by user 1";
    
    int sockfd;
    struct sockaddr_nl local,remote;

    struct nlmsghdr* nlh = NULL;
    struct iovec iov;
    struct msghdr msg;

    int ret;

    /*
    第一個參數必須是 AF_NETLINK 或 PF_NETLINK,在 Linux 中,它們倆實際為一個東西,它表示要使用netlink,
    第二個參數必須是SOCK_RAW或SOCK_DGRAM,因為netlink是一種面向數據包的服務.
    第三個參數指定netlink協議類型,可以是自己在netlink.h中定義的,也可以是內核中已經定義好的。
    */
    sockfd = socket(AF_NETLINK,SOCK_RAW,USER_MSG);
    if(sockfd == -1)
    {
        printf("create socket failure! %s\n",strerror(errno));
        return -1;
    }

    memset(&local,0,sizeof(local));
    local.nl_family = AF_NETLINK;
    local.nl_pid = 50;            //nl_pid 實際上未必是進程 ID,它只是用於區分不同的接收者或發送者的一個標識,可以看做通信雙方端口。
    local.nl_groups = 0;

    ret = bind(sockfd,(struct sockaddr*)&local,sizeof(local));
    if(ret < 0)
    {
        printf("bind() error!\n");
        close(sockfd);
        return -1;
    }

    memset(&remote,0,sizeof(remote));
    remote.nl_family = AF_NETLINK;
    remote.nl_pid = 0;            //為0表示與內核通信
    remote.nl_groups = 0;

    //1.先設置nlh格式和數據
    nlh = (struct nlmsghdr*)malloc(NLMSG_SPACE(MAX_PLOAD));
    if(!nlh)
    {
        printf("malloc nlmsghdr error!\n");
        close(sockfd);
        return -1;
    }

    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 = local.nl_pid;

    memcpy(NLMSG_DATA(nlh),data,strlen(data));

    //2.設置iovec數據
    iov.iov_base = (void*)nlh;
    iov.iov_len = NLMSG_SPACE(MAX_PLOAD);

    //3.設置msg數據
    memset(&msg,0,sizeof(msg));
    msg.msg_name = (void*)&remote;
    msg.msg_namelen = sizeof(remote);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;


    //開始發送數據
    printf("sendmsg start....\n");

    ret = sendmsg(sockfd,&msg,0);
    if(ret < 0)
    {
        perror("send to kernel failure!\n");
        close(sockfd);
        free(nlh);
        exit(-1);
    }

    //接受數據
    printf("recvmsg start....\n");
    ret = recvmsg(sockfd,&msg,0);
    if(ret < 0)
    {
        perror("recv from kernel failure!\n");
        close(sockfd);
        free(nlh);
        exit(-1);        
    }
    printf("recv data:%s\n", (char*)NLMSG_DATA(nlh));

    close(sockfd);
    free((void*)nlh);

    return 0;
}

(二)sendto與recvfrom方法的用戶態實現

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/netlink.h>

#define NETLINK_USER 22
#define USER_MSG (NETLINK_USER+1)

#define MAX_PLOAD 1024

int main(int argc,char** argv)
{
    char* data = "hello kernel space by user 2";
    
    int sockfd,ret;
    struct sockaddr_nl local,remote;

    struct nlmsghdr* nlh = NULL;

    sockfd = socket(AF_NETLINK,SOCK_RAW,USER_MSG);
    if(sockfd == -1)
    {
        printf("create socket failure! %s\n",strerror(errno));
        return -1;
    }

    memset(&local,0,sizeof(local));
    local.nl_family = AF_NETLINK;
    local.nl_pid = 50;
    local.nl_groups = 0;
    if(bind(sockfd,(struct sockaddr*)&local,sizeof(local)) != 0)
    {
        printf("bind() error!\n");
        close(sockfd);
        return -1;
    }

    memset(&remote,0,sizeof(remote));
    remote.nl_family = AF_NETLINK;
    remote.nl_pid = 0;
    remote.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 = local.nl_pid;

    printf("sendmsg start....\n");

    memcpy(NLMSG_DATA(nlh),data,strlen(data));
    ret = sendto(sockfd,nlh,nlh->nlmsg_len,0,(struct sockaddr*)&remote,sizeof(struct sockaddr_nl));

    if(ret<0)
    {
        perror("send to kernel failure!\n");
        close(sockfd);
        exit(-1);
    }

    //接受數據
    printf("recvmsg start....\n");

    memset(nlh,0,NLMSG_SPACE(MAX_PLOAD));
    ret = recvfrom(sockfd,nlh,NLMSG_LENGTH(MAX_PLOAD),0,NULL,NULL);
    if(ret<0)
    {
        printf("recv from kernel failure!\n");
        close(sockfd);
        exit(-1);
    }

    printf("recv data:%s\n",(char*)NLMSG_DATA(nlh));

    close(sockfd);
    free((void*)nlh);

    return 0;
}

(三)兩種情況測試

使用gcc編譯:

gcc ./user.c -o user
gcc ./user2.c -o user2

 查看dmesg結果:

注意:在內核種並不需要實現循環即可一直運行,我們多次調用用戶態程序,依舊可以與內核通信!!!

(四)分析與UDP發送數據包的不同點:

1. socket地址結構不同,UDP為sockaddr_in,Netlink為struct sockaddr_nl;

2. 與UDP發送數據相比,Netlink多了一個消息頭結構struct nlmsghdr需要我們構造。

3.注意sockaddr_nl中的nl_pid,網上很多文章把這個字段說成是進程的pid,其實這完全是望文生義。這里的pid和進程pid沒有什么關系,僅僅相當於UDP的port,用做雙方通信。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM