generic netlink 編程快速入門

目錄

 

一、generic netlink 消息結構

二、接口搭建過程

kernel端程序的準備

1、定義想要傳送的消息種類

2、定義一組命令

3、爲每個命令定義一個響應函數

4、將命令與相應函數關聯起來

5、創建一個命令族

6、向內核註冊新建的命令族

7、將關聯好的命令和響應函數綁定(註冊)到命令族上

用戶端程序的使用:

1、新建一個套接字,並綁定上 netlink address

2、向 GENL_ID_CRTL 詢問想通信的命令族的 id

3、等待接收 GENL_ID_CRTL 的返回的查詢結果

4、拆分讀取接收到的信息

5、與想要通信的命令族通信

三、程序示例

用戶態程序

內核態程序

 


一、generic netlink 消息結構

消息在以流的形式在程序之間進行傳遞,一個流中可能包含多個消息。

對於每個消息消息來說,爲了便於維護和方便使用,還需要一些有關記錄消息的信息。一個 netlink message 結構如下:

其中 nlmsghdr 結構裏記錄了該條 netlink message 的如下信息:

struct nlmsghdr {
	__u32		nlmsg_len;	//該 netlink message 的長度(包括 nlmsghdr 部分本身)
	__u16		nlmsg_type;	//該 netlink message 的種類
	__u16		nlmsg_flags; //一個附加的標記
	__u32		nlmsg_seq;	//該 netlink message 在消息流中的序列號
	__u32		nlmsg_pid;	//發送該 netlink message 的進程的進程號 pid
};

爲了使數據對齊,在 payload 兩端可能會塞入了一些填充字段。netlink message 的 payload 結構如下:

這個結構稱作 family。進程間通信的目的通常是希望信息接收方能夠執行某些動作,而很多命令都是圍繞同一個事件的(如對某個表的增、刪、改、查)。因此爲了便於管理,netlink 通信協議將這些相關的一套命令和其對應的操作組織起來,歸於一個family管轄,每個family都有個獨有的名字,該名字需公示出來,以便其他程序與該 family 通信,當 family 在內核完成註冊後,內核會給其分配一個獨有的 id。如果用戶需要結構化的信息,可以在 netlink message 的 payload 中添加可自選的信息頭 user specific header。剩下的 attributes 結構如下:

每個 attribute 即是一個不可再分的最基本信息單元,結構如下:

其中 nlattr 是管理 payload 的信息頭,記錄了 payload 的以下信息:

struct nlattr {
	__u16           nla_len;//attribute 長度,包括 nlattr 本身
	__u16           nla_type;//attribute 的類型
};

payload 部分即是想要傳遞的信息了,如傳遞一句話 “ hello world ”。爲了對齊,payload 兩端可能會塞入 pad。

netlink message 協議使用的信息結構即是以上這些,但對着使用越來越多,內核可以用來分配給 family 的 id 資源開始緊張了。因此 generic netlink 協議在 netlink 協議上又做了一些拓展,即在 netlink message 的 payload 上又做了一層封裝:

該結構即爲 generic netlink message。該信息頭 genlmsghdr 中記錄如下內容。

struct genlmsghdr {
	__u8	cmd;        //命令號
	__u8	version;    //命令版本號
	__u16	reserved;   //保留字段
};

故最終 generic netlink message 的結構如下:

二、接口搭建過程

kernel端程序的準備

1、定義想要傳送的消息種類

/* generic netlink message attributes */
enum {
      MY_ATTR_UNSPEC,
      MY_ATTR_MSG,
      __MY_ATTR_MAX,
};
#define MY_ATTR_MAX (__MY_ATTR_MAX - 1)

你可能有多種消息需要傳遞,使用枚舉類型來管理消息種類是個不錯的辦法。這裏實際上只定義了一個消息種類,即 MY_ATTR_MSG。 根據枚舉類型的特點,在枚舉體最後定義的 __MY_ATTR_MAX 實際上表示了該屬性集的大小,而 MY_ATTR_MAX 則表示了該屬性集中最後一類屬性(即 MY_MTTR_MSG)的編號 1。 

(在 linux 內核中大量的枚舉體的首個元素名均是以 UNSPEC 結尾,在這裏 MY_ATTR_UNSPEC 作爲該屬性集的“一般屬性”,可能只是用來對應一些“空操作”,而並不特指哪一消息類型。——作者猜測)

2、定義一組命令

/* commands 定義命令類型,用戶空間以此來表明需要執行的命令 */
enum {
    MY_CMD_UNSPEC, 
    MY_CMD_1,      //命令
    __MY_CMD_MAX,  //表示命令集的大小,並不是命令
};
#define MY_CMD_MAX (__MY_CMD_MAX - 1)

與消息屬性的定義類似,這裏同樣使用了枚舉類型來定義命令集,但其實這裏只定義了一個命令,即 MY_CMD_1。根據枚舉類型的特點,在枚舉體最後定義的 __MY_CMD_MAX 實際上表示了該命令集的大小,而 MY_CMD_MAX 則表示了該命令集中最後一個命令(即 MY_CMD_1)的編號 1。 

3、爲每個命令定義一個響應函數

generic netlink協議要求命令相應函數的類型是:

static int my_callback_function(struct sk_buff *skb, struct genl_info *info);

4、將命令與相應函數關聯起來

static struct genl_ops my_cmd_ops = {
        .cmd = MY_CMD_1, //命令
        .flags = 0,
        .policy = my_cmd_policy,
        .doit = my_callback_function, //響應函數
        .dumpit = NULL,
};

通過定義一個 genl_ops, 我們可以將一個命令和其對應的響應函數關聯起來。成員 policy 指明瞭該響應函數對其能夠處理的信息的要求。具體如下:

static struct nla_policy my_cmd_policy[MY_ATTRIBUTE_MAX + 1] = {
      [MY_ATTRIBUTE_MSG] = { .type = NLA_NUL_STRING },
};

這裏使用了比較難懂的語法,先來看一下 struct nla_policy 的定義:

struct nla_policy {
	u16		type;
	u16		len;
};

my_cmd_policy 是一個 nal_policy 結構體數組,數組長度爲2(即 MY_ATTRIBUTE_MAX+1) ,即前文定義的 generic netlink message 屬性集的長度(my_cmd_policy[0] 對應 MY_ATTRIBUTE_UNSPEC;my_cmd_policy[1] 對應 MY_ATTRIBUTE_MSG)。在大括號中將 my_cmd_policy[1] 的 type 屬性賦值爲 NLA_NUL_STRING,該宏的意思是除去NULL外的最大字符串長度。

5、創建一個命令族


/* family definition */
static struct genl_family my_cmd_family = {
      .id = GENL_ID_GENERATE,   //這裏不指定family ID,由內核進行分配
      .hdrsize = 0,             //自定義的頭部長度,參考genl數據包結構
      .name = "MY_CMDS",        //這裏定義family的名稱,user program需要根據這個名字來找到對應的family ID。
      .version = 1,             //版本號1
      .maxattr = ATTRIBUTES_MAX,//最大屬性數
};

該命令族的名爲 MY_CMD_FAMILY。這個名字需要事先告知用戶態程序的編寫者,用戶態程序便通過這個名字來表明想跟內核中的哪個命令族進行通信。

在具體的用戶態向內核命令族發送消息的 API 函數中,用戶需要使用 id 來指定想要與之進行通信的內核命令族,而不能直接使用命令族的名字。解決這個問題的辦法是,當一個命令族在內核中註冊完成時,內核會爲其分配一個id。而內核中還存在一個默認的命令族 GENL_ID_CTRL,用戶態程序便可以先向該命令族發送 CTRL_CMD_GETFAMILY 命令來獲取某個命令族的 id(這裏即是內核分配給 MY_CMDS 命令族的 id)。

6、向內核註冊新建的命令族

genl_register_family(&my_cmd_family);

7、將關聯好的命令和響應函數綁定(註冊)到命令族上

genl_register_ops(&my_cmd_family, &my_cmd_ops);

用戶端程序的使用:

1、新建一個套接字,並綁定上 netlink address

//申請一個generic netlink協議的socket
int sockfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
 
//<1>填寫netlink 通信地址 nladdr
struct sockaddr_nl nladdr;
nladdr.nl_family = AF_NETLINK;//填寫要使用的協議
nladdr.nl_pid = getpid();//填寫當前進程的進程號
/*這個是mask值,如果family ID & nl_groups爲0,
 *則這個family的廣播就接收不到,
 *所以這裏設爲0xffffffff就可以接收所有的family消息*/
nladdr.nl_groups = 0xffffffff;
//</1> 
 
//將填寫好的nladdr綁定到申請的sock上
 bind(sockfd, (struct sockaddr *)&nladdr, sizeof(nladdr));
    

2、向 GENL_ID_CRTL 詢問想通信的命令族的 id

此時,這裏用戶態程序要先與命令族GENL_ID_CRTL 進行通信,使用命令族 id 查詢命令 CTRL_CMD_GETFAMILY,發送的 netlink message 的類型是 CTRL_ATTR_FAMILY_NAME,信息的內容即使上文定義的命令族名 "MY_CMDS",信息的長度爲 strlen("MY_CMDS")+1。 具體過程如下

1)創建一個 netlink message

char* my_family_name = "MY_CMDS";
int len = strlen(my_family_name) + 1;

//<1>計算長度爲 lne 的消息封裝成 netlink message 後的長度
//加上 netlink message attributes header 的長度
len = nla_total_size(len);
//加上自定義消息體頭的長度
len += 0;
//加上 generic netlink message header 的長度  
len += GENL_HDRLEN;
//加上 netlink message header 的長度,netlink message 是消息的最後一層封裝
len = NLMSG_SPACE(len);
//</1>

//申請內存空間用於存儲此條 netlink message
unsigned char *buf = genlmsg_alloc(&len);

2)填寫 netlink message header 

struct nlmsghdr *nlh;    
nlh = (struct nlmsghdr *)buf;//獲取buf開頭的 netlink message header 部分
nlh->nlmsg_len = len;//填寫此netlink message的長度
nlh->nlmsg_type = GENL_ID_CTRL;//填寫此netlink message的類型,即命令族 DOC_EXMPL 的 id
nlh->nlmsg_flags = NLM_F_REQUEST;
nlh->nlmsg_seq = 0;//這是第0個 netlink message
nlh->nlmsg_pid = 0;//發送這條 netlink message 的進程的進程號

3)填寫 generic netlink message header

struct genlmsghdr *glh;
/*generic netlink message是netlink message的負載,
 *在該負載中獲取generic netlink message header*/  
glh = (struct genlmsghdr *)NLMSG_DATA(nlh);
glh->cmd = CTRL_CMD_GETFAMILY;//填寫 generic netlink 命令
glh->version = 1;//填寫 generic netlink命令版本號

4)填寫 netlink message attribute header,和想要發送的消息內容

struct nlattr *nla;
//generic netlink message 即是真實的消息部分,消息的格式是 nlattr      
nla = (struct nlattr *)GENLMSG_DATA(glh);
//填寫消息類型,即 DOC_EXMPL_A_MSG
nla->nla_type = CTRL_ATTR_FAMILY_NAME;
//填寫消息長度(包括消息頭和負載)
nla->nla_len = nla_attr_size(strlen(my_family_name)+1);
//將想要傳遞的數據 nla_data = “hello from user space!”填入消息體 nalttr 的負載部分
memcpy(NLA_DATA(nla), my_family_name, nla_len);

5) 填寫要使用的 netlink socket address

struct sockaddr_nl nladdr;
memset(&nladdr, 0, sizeof(nladdr));
nladdr.nl_family = AF_NETLINK;//使用 netlink 通信協議

6)發送填寫完畢的 netlink message

int ret;
int count = 0;
do {
    ret = sendto(sockfd, &buf[count], len - count, 0,
                        (struct sockaddr *)&nladdr, sizeof(nladdr));
    count += ret;
}while (count < len);

使用套接字sockfd(地址爲nladdr),嘗試傳遞存儲在以 buf[count] 爲起始位置,長度爲 len 的消息。但在實際傳送過過程中可能無法一次性傳遞完存儲在 buf 區的所有內容,sendto 函數會返回此次調用實際傳送的消息長度,我們可以使用上面這個循環直至將 buf 區的消息全部送出。

3、等待接收 GENL_ID_CRTL 的返回的查詢結果

1)新建一個 netlink message,用於存儲 GENL_ID_CRTL 返回的信息

這裏一般給待接收的信息預留 len = 256 字節的空間,創建 netlink message 的過程見上文

2)填寫“收件地址” sockaddr_nl

struct sockaddr_nl nladdr; 
nladdr.nl_family = AF_NETLINK;//使用 netlink 通信協議
nladdr.nl_pid = getpid();     //填寫當前進程的進程號
nladdr.nl_groups = 0xffffffff;//此廣播號意味着可以接收所有內核命令族發來的信息

3)設置數據接收區,綁定“收件地址”,等待接收內核命令族發出的信息

struct iovec iov;
iov.iov_base = buf;
iov.iov_len = len;
 
struct msghdr msg;
msg.msg_name = (void *)&nladdr;
msg.msg_namelen = sizeof(nladdr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = NULL;
msg.msg_controllen = 0;
msg.msg_flags = 0;
//</1>    
 
//從套接字sockfd中接收消息,存儲到msg
recvmsg(sockfd, &msg, 0);

4、拆分讀取接收到的信息

//讀取到的數據流裏面,可能會包含多條nlmsg
for (struct nlmsghdr *nlh = (struct nlmsghdr *)buf; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len))
{
    /* The end of multipart message. */
    struct genlmsghdr *glh = (struct genlmsghdr *)NLMSG_DATA(nlh);
    struct nlattr *nla = (struct nlattr *)GENLMSG_DATA(glh);   //the first attribute
    int nla_len = nlh->nlmsg_len - GENL_HDRLEN;           //len of attributes
    //一條nlmsg裏面,可能會包含多個attr
    for (int i = 0; NLA_OK(nla, nla_len); nla = NLA_NEXT(nla, nla_len), ++i)
    {
        //如果消息裏的nla_type與調用者指定的nla_type相同
        if (nla_type == nla->nla_type)
        {
            int l = nla->nla_len - NLA_HDRLEN;  
            *len = *len > l ? l : *len;
            //將該 nlattr 對應的數據拷貝給到調用者指定的 buf 中
            memcpy(buf, NLA_DATA(nla), *len);
            break;
        }
    }
}

此時 buf 中存儲的就是內核分配給命令族 MY_CMDS 的 id 號

5、與想要通信的命令族通信

該通信過程與上文所述的用戶態程序與命令族 GENL_ID_CRTL 的通信過程相同,不再贅述。不同的是:

  • 填寫 netlink message header 的 nlmsg_type 成員時,使用 MY_CMDS,而不是 GENL_ID_CTRL
  • 填寫 generic netlink message header 的 cmd 成員時,使用 MY_CMD_1,而不是 CTRL_CMD_FAMILY
  • 填寫 netlink message attribute header 的 nla_type 成員時,使用 MY_ATTR_MSG,而不是 CTRL_ATTR_FAMILY_NAME

三、程序示例

注:爲了程序易讀好懂,這裏改變了函數定義的順序,刪去了差錯校驗代碼。完整程序可參考:

https://blog.csdn.net/ty3219/article/details/63683698

用戶態程序

int main(int argc, char *argv[])
{
    test_netlink_unicast(); 
    return 0;
}

test_netlink_unicast

#define BUF_SIZE    256
void test_netlink_unicast(void)
{
    //獲取用於與內核通信的socket,並綁定上自己的netlink通信地址nladdr
    int sockfd = genlmsg_open();
    
    //<1>向kernel發送信息
    //這裏必須先通過family的名字獲取到family ID,名字需要與驅動裏的一致
    int id = genlmsg_get_family_id(sockfd, "DOC_EXMPL");
    //獲取自己的進程號pid
    pid_t pid = getpid();
    //向內核發送genl消息
    /*通過sockfd套接字,向 DOC_EXMPL 命令族,
     *本進程號是 pid,該命令是 DOC_EXMPL_C_ECHO(命令版本號爲1),
     *命令負載的信息種類是 DOC_EXMPL_A_MSG,
     *信息內容爲MESSAGE_TO_KERNEL,長度爲strlen(MESSAGE_TO_KERNEL) + 1*/
    genlmsg_send(sockfd, id, pid, DOC_EXMPL_C_ECHO, 1,
                        DOC_EXMPL_A_MSG, MESSAGE_TO_KERNEL, strlen(MESSAGE_TO_KERNEL) + 1);
    //</1>
    
    //<2>接收從kernel傳來的信息
    /*申請一個長度爲len的內存空間,用於存儲netlink message,
     *但以netlink header類型指針指示這段空間的起始位置*/
    int len = BUF_SIZE;
    struct nlmsghdr *nlh = genlmsg_alloc(&len);
    //使用sockfd進行通信,將接收到的內核消息存儲在剛申請的內存空間中
    nlh_len = genlmsg_recv(sockfd, (unsigned char *)nlh, len);
    //</2> 
   
    unsigned char buf[BUF_SIZE];
    genlmsg_dispatch(nlh, nlh_len, DOC_EXMPL_A_MSG, buf, &len);
}

genlmsg_open

static int genlmsg_open(void)
{    
    //申請一個generic netlink協議的socket
    int sockfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
 
    //<1>填寫netlink 通信地址 nladdr
    struct sockaddr_nl nladdr;
    nladdr.nl_family = AF_NETLINK;//填寫要使用的協議
    nladdr.nl_pid = getpid();//填寫當前進程的進程號
    /*這個是mask值,如果family ID & nl_groups爲0,
     *則這個family的廣播就接收不到,
     *所以這裏設爲0xffffffff就可以接收所有的family消息*/
    nladdr.nl_groups = 0xffffffff;
    //</1> 
 
    //將填寫好的nladdr綁定到申請的sock上
    bind(sockfd, (struct sockaddr *)&nladdr, sizeof(nladdr));

    return sockfd;
}

genlmsg_get_family_id

static int genlmsg_get_family_id(int sockfd, const char *family_name)
{
    //<1>向GENL_ID_CTRL命令族查詢內核給family_name分配的id
    /*通過sockfd套接字,向GENL_ID_CTRL命令族,
     *進程號是 0,該命令是CTRL_CMD_GETFAMILY(命令版本號爲1),
     *命令負載的信息種類是CTRL_ATTR_FAMILY_NAME,
     *信息內容爲family_name,長度爲strlen(family_name) + 1*/
    genlmsg_send(sockfd, GENL_ID_CTRL, 0, CTRL_CMD_GETFAMILY, 1,
            CTRL_ATTR_FAMILY_NAME, family_name, strlen(family_name) + 1);
    //</1>

    //<2>接收sockfd上收到的內核返回的信息,存儲在buf中
    int len = 256;
    void *buf = genlmsg_alloc(&len); 
    len = genlmsg_recv(sockfd, buf, len);
    //</2>
     
    //<3>拆解從內核傳來的信息,得到family_name對應的id
    __u16 id = 0;
    int l = sizeof(id);
    genlmsg_dispatch((struct nlmsghdr *)buf, len, 0, CTRL_ATTR_FAMILY_ID, (unsigned char *)&id, &l);
    //</3>
 
    return id;
}

genlmsg_send

static int genlmsg_send(int sockfd, unsigned short nlmsg_type, unsigned int nlmsg_pid,
        unsigned char genl_cmd, unsigned char genl_version,
        unsigned short nla_type, const void *nla_data, unsigned int nla_len)
{
    /*申請一個長度爲nla_len的內存空間,用於存儲netlink message,
     *但以最簡單的unsigned char類型指針指示這段空間的起始位置*/
    int len = nla_len;
    unsigned char *buf = genlmsg_alloc(&len);
    
    //<1>填寫netlink message header
    struct nlmsghdr *nlh;    
    nlh = (struct nlmsghdr *)buf;//獲取buf開頭的 netlink message header 部分
    nlh->nlmsg_len = len;//填寫此netlink message的長度
    nlh->nlmsg_type = nlmsg_type;//填寫此netlink message的類型,即命令族 DOC_EXMPL 的 id
    nlh->nlmsg_flags = NLM_F_REQUEST;
    nlh->nlmsg_seq = 0;
    nlh->nlmsg_pid = nlmsg_pid;
    //</1>
 
    //<2>填寫generic netlink message header
    struct genlmsghdr *glh;
    /*generic netlink message是netlink message的負載,
     *在該負載中獲取generic netlink message header*/  
    glh = (struct genlmsghdr *)NLMSG_DATA(nlh);
    glh->cmd = genl_cmd;//填寫 generic netlink 命令,即 DOC_EXMPL_C_ECHO
    glh->version = genl_version;//這裏 generic netlink命令版本號是 1
    //</2>
     
    //<3>netlink attribute header
    struct nlattr *nla;
    //generic netlink message即是真實的消息部分,消息的格式是nlattr      
    nla = (struct nlattr *)GENLMSG_DATA(glh);
    //填寫消息類型,即 DOC_EXMPL_A_MSG
    nla->nla_type = nla_type;
    //填寫消息長度(包括消息頭和負載)
    nla->nla_len = nla_attr_size(nla_len);
    //將想要傳遞的數據 nla_data = “hello from user space!”填入消息體 nalttr 的負載部分
    memcpy(NLA_DATA(nla), nla_data, nla_len);
    //</3>     

    //<4>填寫消息發送的netlink socket address
    struct sockaddr_nl nladdr;
    memset(&nladdr, 0, sizeof(nladdr));
    nladdr.nl_family = AF_NETLINK;
    //</4>
 
    int ret;
    int count = 0;
    do {
        /*使用套接字sockfd(地址爲nladdr),嘗試傳遞存儲在buf區,長度爲len的消息,
         *sendto會返回實際傳送的消息長度,使用循環直至將buf區的消息全部送出*/
        ret = sendto(sockfd, &buf[count], len - count, 0,
                        (struct sockaddr *)&nladdr, sizeof(nladdr));
        count += ret;
    }while (count < len);
 
    return count;
}

genlmsg_alloc

static void *genlmsg_alloc(int *size)
{
    //<1>計算長度爲size的消息封裝成netlink message後的長度
    int len;
    //加上nlattr的長度(nalttr是最基本的消息體頭,其後跟的負載部分再無任何封裝)
    len = nla_total_size(*size);
    //加上自定義消息體頭的長度
    len += 0;
    //加上generic netlink message header 的長度  
    len += GENL_HDRLEN;
    //加上netlink message header 的長度,netlink message 是消息的最後一層封裝
    len = NLMSG_SPACE(len);
    //</1>
 
    //告知申請者封裝之後的netlink message的長度
    *size = len;
    //申請netlink message所需的內存空間
    unsigned char *buf = malloc(len);
    return buf;
}

genlmsg_recv

//從套接字sockfd接收從內核傳來的消息,存儲在buf(長度爲len)中
static int genlmsg_recv(int sockfd, unsigned char *buf, unsigned int len)
{
    //<1>設置好message header,準備接收消息
    //填寫“收件地址”
    struct sockaddr_nl nladdr; 
    nladdr.nl_family = AF_NETLINK;
    nladdr.nl_pid = getpid();
    nladdr.nl_groups = 0xffffffff;
     
    //設置好數據接收區
    struct iovec iov;
    iov.iov_base = buf;
    iov.iov_len = len;
 
    struct msghdr msg;
    msg.msg_name = (void *)&nladdr;
    msg.msg_namelen = sizeof(nladdr);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    msg.msg_control = NULL;
    msg.msg_controllen = 0;
    msg.msg_flags = 0;
    //</1>    

    //從套接字sockfd中接收消息,存儲到msg
    recvmsg(sockfd, &msg, 0);
}

genlmsg_dispatch

static int genlmsg_dispatch(struct nlmsghdr *nlmsghdr, unsigned int nlh_len,
                            int nlmsg_type, int nla_type, unsigned char *buf, int *len)
{
    //讀取到的數據流裏面,可能會包含多條nlmsg
    for (struct nlmsghdr *nlh = nlmsghdr; NLMSG_OK(nlh, nlh_len); nlh = NLMSG_NEXT(nlh, nlh_len))
    {
        /* The end of multipart message. */
        struct genlmsghdr *glh = (struct genlmsghdr *)NLMSG_DATA(nlh);
        struct nlattr *nla = (struct nlattr *)GENLMSG_DATA(glh);   //the first attribute
        int nla_len = nlh->nlmsg_len - GENL_HDRLEN;           //len of attributes
        //一條nlmsg裏面,可能會包含多個attr
        for (int i = 0; NLA_OK(nla, nla_len); nla = NLA_NEXT(nla, nla_len), ++i)
        {
            //如果消息裏的nla_type與調用者指定的nla_type相同
            if (nla_type == nla->nla_type)
            {
                int l = nla->nla_len - NLA_HDRLEN;  
                *len = *len > l ? l : *len;
                //將該nlattr對應的數據拷貝給到調用者指定的buf中
                memcpy(buf, NLA_DATA(nla), *len);
                break;
            }
        }
    }
}

內核態程序

static int genetlink_init(void)
{
    //註冊一個命令族,linux 3.12纔開始引入generic netlink機制
    genl_register_family(&doc_exmpl_genl_family);   
    //給註冊的命令族綁定上對應的操作集
    genl_register_ops(&doc_exmpl_genl_family, &doc_exmpl_genl_ops_echo);   
    /*
     * for multicast
     */
    genl_register_mc_group(&doc_exmpl_genl_family, &doc_exmpl_genl_mcgrp);

}

doc_exmpl_genl_family

/* family definition */
static struct genl_family doc_exmpl_genl_family = {
      .id = GENL_ID_GENERATE,   //這裏不指定family ID,由內核進行分配
      .hdrsize = 0,             //自定義的頭部長度,參考genl數據包結構
      .name = "DOC_EXMPL",      //這裏定義family的名稱,user program需要根據這個名字來找到對應的family ID。
      .version = 1,
      .maxattr = DOC_EXMPL_A_MAX,
};

doc_exmpl_genl_ops_echo


/* operation definition 將命令command echo和具體的handler對應起來 */
static struct genl_ops doc_exmpl_genl_ops_echo = {
        .cmd = DOC_EXMPL_C_ECHO,
        .flags = 0,
        .policy = doc_exmpl_genl_policy,
        .doit = doc_exmpl_echo,
        .dumpit = NULL,
};

doc_exmpl_genl_policy

/* netlink attributes */
enum {
      DOC_EXMPL_A_UNSPEC,
      DOC_EXMPL_A_MSG,
      __DOC_EXMPL_A_MAX,
};
#define DOC_EXMPL_A_MAX (__DOC_EXMPL_A_MAX - 1)
 
/* attribute policy */
static struct nla_policy doc_exmpl_genl_policy[DOC_EXMPL_A_MAX + 1] = {
      [DOC_EXMPL_A_MSG] = { .type = NLA_NUL_STRING },
};

nla_policy

struct nla_policy {
	u16		type;
	u16		len;
};

doc_exmpl_echo

//echo command handler, 命令處理函數,當接收到user program發出的命令後,這個函數會被內核調用
#define MY_TEST_MSG   "Hello from kernel space!!!"
#define MSG_LEN       (strlen(MY_TEST_MSG) + 1)
static int doc_exmpl_echo(struct sk_buff *skb, struct genl_info *info)
{
    //新建一個 netlink message
    /* total length of attribute including padding */
    size_t size = nla_total_size(MSG_LEN);    
    struct sk_buff *skbp = genlmsg_new(size, GFP_KERNEL);

    /* Add a new netlink message to an skb */
    struct nlmsghdr *nlhdr = nlmsg_hdr(skb);
    pid_t pid = nlhdr->nlmsg_pid;
    
    genlmsg_put(skbp, pid, 0, &doc_exmpl_genl_family, 0, DOC_EXMPL_C_ECHO);
    
    nla_put(skbp, DOC_EXMPL_A_MSG, MSG_LEN, MY_TEST_MSG);

    void *head = genlmsg_data(nlmsg_data(nlmsg_hdr(skbp)));
    //
    genlmsg_end(skbp, head);

    genlmsg_unicast(&init_net, skbp, pid);

}

doc_exmpl_genl_genl_mcgrp


static struct genl_multicast_group doc_exmpl_genl_mcgrp = {
        .name = "DOC_EXMPL_GRP",
};

小結

內核態程序

接收:註冊 genl_ops,填寫命令和相關的響應函數。等待用戶態程序發送該命令後,由內核調用相關的響應函數

發送:genlmsg_unicast / genlmsg_multicast

用戶態程序

接收:recvmsg

發送:sendto

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章