相关部分可以参考之前文章:《netlink机制》
因为Netlink协议簇不能超过32个,而且添加协议簇需要在include/linux/netlink.h中添加协议簇定义。因为开发了通用netlink,类似netlink的多路复用器,因为提供了通用的渠道,所以可以在其他子系统使用,例如ACPI、任务统计信息、过热事件等。
1.1.1 协议族
通用link协议由family来管理,其中genl_ctrl是特殊的family由通用netlink自己注册和实现,并用来查询Family列表、管理添加、删除等事件。
通用link的协议族结构体如下:
struct genl_family {
int id; /* private */
unsigned int hdrsize;
char name[GENL_NAMSIZ];
unsigned int version;
unsigned int maxattr;
bool netnsok;
bool parallel_ops;
int (*pre_doit)(const struct genl_ops *ops,
struct sk_buff *skb,
struct genl_info *info);
void (*post_doit)(const struct genl_ops *ops,
struct sk_buff *skb,
struct genl_info *info);
int (*mcast_bind)(struct net *net, int group);
void (*mcast_unbind)(struct net *net, int group);
struct nlattr ** attrbuf; /* private */
const struct genl_ops * ops;
const struct genl_multicast_group *mcgrps;
unsigned int n_ops;
unsigned int n_mcgrps;
unsigned int mcgrp_offset; /* private */
struct module *module;
};
定义在include/net/genetlink.h
1.1.2 命令处理结构
位于文件:include/net/genetlink.h
定义如下:
/**
* struct genl_ops - generic netlink operations
* @cmd: command identifier
* @internal_flags: flags used by the family
* @flags: flags
* @policy: attribute validation policy
* @doit: standard command callback
* @start: start callback for dumps
* @dumpit: callback for dumpers
* @done: completion callback for dumps
*/
struct genl_ops {
const struct nla_policy *policy;
int (*doit)(struct sk_buff *skb,
struct genl_info *info);
int (*start)(struct netlink_callback *cb);
int (*dumpit)(struct sk_buff *skb,
struct netlink_callback *cb);
int (*done)(struct netlink_callback *cb);
u8 cmd;
u8 internal_flags;
u8 flags;
};
1.1.3 内核接收消息结构
内核在接收到用户的genetlink消息后,会对消息解析并封装成genl_info结构,便于命令回校函数进行处理
/**
* struct genl_info - receiving information
* @snd_seq: sending sequence number
* @snd_portid: netlink portid of sender
* @nlhdr: netlink message header
* @genlhdr: generic netlink message header
* @userhdr: user specific header
* @attrs: netlink attributes
* @_net: network namespace
* @user_ptr: user pointers
* @extack: extended ACK report struct
*/
struct genl_info {
u32 snd_seq;
u32 snd_portid;
struct nlmsghdr * nlhdr;
struct genlmsghdr * genlhdr;
void * userhdr;
struct nlattr ** attrs;
possible_net_t _net;
void * user_ptr[2];
struct netlink_ext_ack *extack;
};
1.1.4 初始化过程
通用netlink初始化函数为genl_init(),位于net/netlink/genetlink.c文件。
static int __init genl_init(void)
{
int err;
err = genl_register_family(&genl_ctrl);
if (err < 0)
goto problem;
err = register_pernet_subsys(&genl_pernet_ops);
if (err)
goto problem;
return 0;
problem:
panic("GENL: Cannot register controller: %d\n", err);
}
1.1.4.1 genl_register_family
该函数先通过genl_validate_ops来检测协议的合法性,
对于每一个注册的genl_ops结构,其中doit和dumpit回调函数必须至少实现一个,然后其针对的cmd命令不可以出现重复,否则返回错误,注册失败。
接着上锁启动链表操作,寻找是否已经有同名协议注册,然后分配ID,如果是GENL_ID_CTRL就是0X10。
接着根据注册的最大attr参数maxattr,这里对于genl_ctrl来说一共分配了CTRL_ATTR_MAX+1个指针内存空间,以后用于缓存attr属性地址。
调用genl_validate_assign_mc_groups()函数判断新增组播地址空间,判断注册family的group组播名的有效性;为该family分配组播地址比特位并将bit偏移保存到family->mcgrp_offset变量中。
最后调用genl_ctrl_event()函数向内核的控制器family发送CTRL_CMD_NEWFAMILY和CTRL_CMD_NEWMCAST_GRP命令消息。
1.1.4.2 register_pernet_subsys
register_pernet_subsys方法为在系统中注册网络命名空间子系统
1.1.4.3 相关结构体
其中genl_pernet_ops结构体如下:
static struct pernet_operations genl_pernet_ops = {
.init = genl_pernet_init,
.exit = genl_pernet_exit,
};
其中genl_ctrl协议族的ID是固定的为0x10。
static struct genl_family genl_ctrl __ro_after_init = {
.module = THIS_MODULE,
.ops = genl_ctrl_ops,
.n_ops = ARRAY_SIZE(genl_ctrl_ops),
.mcgrps = genl_ctrl_groups,
.n_mcgrps = ARRAY_SIZE(genl_ctrl_groups),
.id = GENL_ID_CTRL,
.name = "nlctrl",
.version = 0x2,
.maxattr = CTRL_ATTR_MAX,
.netnsok = true,
};
其中CTRL_ATTR_MAX表示支持的attr属性最大个数。属性类型定义如下:
enum {
CTRL_ATTR_UNSPEC,
CTRL_ATTR_FAMILY_ID,
CTRL_ATTR_FAMILY_NAME,
CTRL_ATTR_VERSION,
CTRL_ATTR_HDRSIZE,
CTRL_ATTR_MAXATTR,
CTRL_ATTR_OPS,
CTRL_ATTR_MCAST_GROUPS,
__CTRL_ATTR_MAX,
};
其中genl_multicast_group结构体如下,添加了name为”notify“的组播组:
static const struct genl_multicast_group genl_ctrl_groups[] = {
{ .name = "notify", },
};
其中genl_ctrl中genl_pernet_ops结构体为操作函数集合,如下:
static const struct genl_ops genl_ctrl_ops[] = {
{
.cmd = CTRL_CMD_GETFAMILY,
.doit = ctrl_getfamily,
.dumpit = ctrl_dumpfamily,
.policy = ctrl_policy,
},
};
这里协议定义了内核操作接口为CTRL_CMD_GETFAMILY,用于应用空间从内核中获取指定family名称的ID号。attr有效性策略为ctrl_policy。
static const struct nla_policy ctrl_policy[CTRL_ATTR_MAX+1] = {
[CTRL_ATTR_FAMILY_ID] = { .type = NLA_U16 },
[CTRL_ATTR_FAMILY_NAME] = { .type = NLA_NUL_STRING,
.len = GENL_NAMSIZ - 1 },
};
关于genl_init()函数在系统启动过程中合适被调用此文就不展开,后续作为专题来描述。
1.1.5 使用
使用通用netlink套接字可以采取如下:
l  创建一个genl_family对象,并调用genl_register_family来注册
l  创建一个genl_ops对象,并调用genl_register_ops来注册。
或者直接使用genl_register_family_with_ops,并传递一个genl_family对象、genl_ops数组及长度。
创建通用netlink内核套接字后,需要注册控制器簇(genl_ctrl),id是固定的0x10,它也是genl_family的对象。其他的簇的id都是初始化为GENL_ID_GENERATE,并动态分配。
用户空间如果需要和内核空间交互,需要先根据family name请求到相应的family ID,而后进行相互沟通。
1.1.6 消息结构详细信息
更加详细信息可以观察文件include/net/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)---^
*
* Data Structures:
* struct nlmsghdr netlink message header
*
* Message Construction:
* nlmsg_new() create a new netlink message
* nlmsg_put() add a netlink message to an skb
* nlmsg_put_answer() callback based nlmsg_put()
* nlmsg_end() finalize netlink message
* nlmsg_get_pos() return current position in message
* nlmsg_trim() trim part of message
* nlmsg_cancel() cancel message construction
* nlmsg_free() free a netlink message
*
* Message Sending:
* nlmsg_multicast() multicast message to several groups
* nlmsg_unicast() unicast a message to a single socket
* nlmsg_notify() send notification message
*
* Message Length Calculations:
* nlmsg_msg_size(payload) length of message w/o padding
* nlmsg_total_size(payload) length of message w/ padding
* nlmsg_padlen(payload) length of padding at tail
*
* Message Payload Access:
* nlmsg_data(nlh) head of message payload
* nlmsg_len(nlh) length of message payload
* nlmsg_attrdata(nlh, hdrlen) head of attributes data
* nlmsg_attrlen(nlh, hdrlen) length of attributes data
*
* Message Parsing:
* nlmsg_ok(nlh, remaining) does nlh fit into remaining bytes?
* nlmsg_next(nlh, remaining) get next netlink message
* nlmsg_parse() parse attributes of a message
* nlmsg_find_attr() find an attribute in a message
* nlmsg_for_each_msg() loop over all messages
* nlmsg_validate() validate netlink message incl. attrs
* nlmsg_for_each_attr() loop over all attributes
*
* Misc:
* nlmsg_report() report back to application?
*
* ------------------------------------------------------------------------
* 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)-----------------------------'
*
* Data Structures:
* struct nlattr netlink attribute header
*
* Attribute Construction:
* nla_reserve(skb, type, len) reserve room for an attribute
* nla_reserve_nohdr(skb, len) reserve room for an attribute w/o hdr
* nla_put(skb, type, len, data) add attribute to skb
* nla_put_nohdr(skb, len, data) add attribute w/o hdr
* nla_append(skb, len, data) append data to skb
*
* Attribute Construction for Basic Types:
* nla_put_u8(skb, type, value) add u8 attribute to skb
* nla_put_u16(skb, type, value) add u16 attribute to skb
* nla_put_u32(skb, type, value) add u32 attribute to skb
* nla_put_u64_64bit(skb, type,
* value, padattr) add u64 attribute to skb
* nla_put_s8(skb, type, value) add s8 attribute to skb
* nla_put_s16(skb, type, value) add s16 attribute to skb
* nla_put_s32(skb, type, value) add s32 attribute to skb
* nla_put_s64(skb, type, value,
* padattr) add s64 attribute to skb
* nla_put_string(skb, type, str) add string attribute to skb
* nla_put_flag(skb, type) add flag attribute to skb
* nla_put_msecs(skb, type, jiffies,
* padattr) add msecs attribute to skb
* nla_put_in_addr(skb, type, addr) add IPv4 address attribute to skb
* nla_put_in6_addr(skb, type, addr) add IPv6 address attribute to skb
*
* Nested Attributes Construction:
* nla_nest_start(skb, type) start a nested attribute
* nla_nest_end(skb, nla) finalize a nested attribute
* nla_nest_cancel(skb, nla) cancel nested attribute construction
*
* Attribute Length Calculations:
* nla_attr_size(payload) length of attribute w/o padding
* nla_total_size(payload) length of attribute w/ padding
* nla_padlen(payload) length of padding
*
* Attribute Payload Access:
* nla_data(nla) head of attribute payload
* nla_len(nla) length of attribute payload
*
* Attribute Payload Access for Basic Types:
* nla_get_u8(nla) get payload for a u8 attribute
* nla_get_u16(nla) get payload for a u16 attribute
* nla_get_u32(nla) get payload for a u32 attribute
* nla_get_u64(nla) get payload for a u64 attribute
* nla_get_s8(nla) get payload for a s8 attribute
* nla_get_s16(nla) get payload for a s16 attribute
* nla_get_s32(nla) get payload for a s32 attribute
* nla_get_s64(nla) get payload for a s64 attribute
* nla_get_flag(nla) return 1 if flag is true
* nla_get_msecs(nla) get payload for a msecs attribute
*
* Attribute Misc:
* nla_memcpy(dest, nla, count) copy attribute into memory
* nla_memcmp(nla, data, size) compare attribute with memory area
* nla_strlcpy(dst, nla, size) copy attribute to a sized string
* nla_strcmp(nla, str) compare attribute with string
*
* Attribute Parsing:
* nla_ok(nla, remaining) does nla fit into remaining bytes?
* nla_next(nla, remaining) get next netlink attribute
* nla_validate() validate a stream of attributes
* nla_validate_nested() validate a stream of nested attributes
* nla_find() find attribute in stream of attributes
* nla_find_nested() find attribute in nested attributes
* nla_parse() parse and validate stream of attrs
* nla_parse_nested() parse nested attribuets
* nla_for_each_attr() loop over all attributes
* nla_for_each_nested() loop over the nested attributes
*=========================================================================
genl数据包的结构如下图:
genl机制的数据包分了4层,用户的实际数据封装在attribute里,一个或多个attribute可以被封装在用户自定义的一个family报文里,一个family报文又被封装在genlmsg里,最后genlmsg被封装在nlmsg里,总共4层。
1.1.7 源码一
1.1.7.1 内核代码
几个重要的数据数据结构关系如下:
内核代码中初始化一个自定义协议NETLINK_USER并初始化,然后指定数据回调函数hello_nl_recv_msg,该函数只是简单发送一个字符串“hello,from kernel”,如下:
#include <linux/module.h>
#include <net/sock.h>
#include <linux/netlink.h>
#include <linux/skbuff.h>
#define NETLINK_USER 31 //the user defined channel, the key factor
struct sock *nl_sk = NULL;
static void hello_nl_recv_msg(struct sk_buff *skb)
{
struct nlmsghdr *nlh;
int pid;
struct sk_buff *skb_out;
int msg_size;
char *msg="hello,from kernel";
int res;
printk(KERN_INFO "Entering: %s\n", __FUNCTION__);
msg_size=strlen(msg);
//for receiving...
nlh=(struct nlmsghdr*)skb->data; //nlh message comes from skb's data... (sk_buff: unsigned char *data)
/* static inline void *nlmsg_data(const struct nlmsghdr *nlh)
{
return (unsigned char *) nlh + NLMSG_HDRLEN;
}
nlmsg_data - head of message payload */
printk(KERN_INFO "Netlink received msg payload: %s\n",(char*)nlmsg_data(nlh));
//for sending...
pid = nlh->nlmsg_pid; // Sending process port ID, will send new message back to the 'user space sender'
skb_out = nlmsg_new(msg_size,0); //nlmsg_new - Allocate a new netlink message: skb_out
if(!skb_out)
{
printk(KERN_ERR "Failed to allocate new skb\n");
return;
}
nlh=nlmsg_put(skb_out,0,0,NLMSG_DONE,msg_size,0);
* nlmsg_put - Add a new netlink message to an skb
* @skb: socket buffer to store message in
* @portid: netlink PORTID of requesting application
* @seq: sequence number of message
* @type: message type
* @payload: length of message payload
* @flags: message flags
//#define NETLINK_CB(skb) (*(struct netlink_skb_parms*)&((skb)->cb))
//cb: This is the control buffer. It is free to use for every layer. Please put your private variables there
/* struct netlink_skb_parms {
struct ucred creds; // Skb credentials
__u32 pid;
__u32 dst_group;
}; */
//map skb->cb (char cb[48] __aligned(8); control buffer) to "struct netlink_skb_parms", so it has field pid/dst_group
//so there should be convention: cb[48] is divided into creds/pid/dst_group...to convey those info
NETLINK_CB(skb_out).dst_group = 0; /* not in mcast group */
strncpy(nlmsg_data(nlh),msg,msg_size); //char *strncpy(char *dest, const char *src, size_t count)
//msg "Hello from kernel" => nlh -> skb_out
res=nlmsg_unicast(nl_sk,skb_out,pid); //nlmsg_unicast - unicast a netlink message
//@pid: netlink pid of the destination socket
if(res<0)
printk(KERN_INFO "Error while sending bak to user\n");
}
static int __init hello_init(void)
{
//struct net init_net; defined in net_namespace.c
//unit=NETLINK_USER: refer to some kernel examples
//groups = 0, unicast
//nl_sk: global sock, will be sent to hello_nl_recv_msg as argument (nl_sk ->...-> skb) and return from below func, by Tom Xue, not totally verified
struct netlink_kernel_cfg cfg = {
.input = hello_nl_recv_msg,//该函数原型可参考内核代码,其他参数默认即可
};
nl_sk = netlink_kernel_create(&init_net, NETLINK_USER, &cfg);
printk("Entering: %s\n",__FUNCTION__);
if(!nl_sk)
{
printk(KERN_ALERT "Error creating socket.\n");
return -10;
}
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_INFO "exiting hello module\n");
netlink_kernel_release(nl_sk);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
1.1.7.2 用户态代码
如下:
#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 NETLINK_USER 31 //self defined
#define MAX_PAYLOAD 1024 /* maximum payload size*/
struct sockaddr_nl src_addr, dest_addr;
struct nlmsghdr *nlh = NULL;
struct iovec iov;
int sock_fd;
struct msghdr msg; //msghdr includes: struct iovec * msg_iov;
void main()
{
//int socket(int domain, int type, int protocol);
sock_fd=socket(PF_NETLINK, SOCK_RAW, NETLINK_USER);
if(sock_fd<0)
return -1;
memset(&src_addr, 0, sizeof(src_addr));
src_addr.nl_family = AF_NETLINK;
src_addr.nl_pid = getpid(); /* self pid */
//int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));
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: contains "Hello"
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;
strcpy(NLMSG_DATA(nlh), "Hello"); //put "Hello" into nlh
iov.iov_base = (void *)nlh; //iov -> nlh
iov.iov_len = nlh->nlmsg_len;
msg.msg_name = (void *)&dest_addr; //msg_name is Socket name: dest
msg.msg_namelen = sizeof(dest_addr);
msg.msg_iov = &iov; //msg -> iov
msg.msg_iovlen = 1;
printf("Sending message to kernel\n");
sendmsg(sock_fd,&msg,0); //msg -> find the (destination) socket name: dest
//msg -> iov -> nlh -> "Hello"
printf("Waiting for message from kernel\n");
/* Read message from kernel */
recvmsg(sock_fd, &msg, 0); //msg is also receiver for read
printf("Received message payload: %s\n", NLMSG_DATA(nlh)); //msg -> iov -> nlh
close(sock_fd);
}
1.1.8 源码二
1.1.8.1 内核
代码中定义两种类型的Genetlink cmd指令,其中DEMO_CMD_ECHO用于应用层下发数据,DEMO_CMD_REPLY用于内核向应用层回发数据;同时定义两种类型的attr属性参数,其中DEMO_CMD_ATTR_MESG表示字符串,DEMO_CMD_ATTR_DATA表示数据。
定义协议demo_family,其中ID号为GENL_ID_GENERATE,表示由内核统一分配,maxattr为DEMO_CMD_ATTR_MAX,其前文中定义的最大attr属性数,内核将为其分配缓存空间。
定义操作函数集:demo_ops,只有DEMO_CMD_ECHO类型的cmd创建消息处理函数接口(因为DEMO_CMD_REPLY类型的cmd用于内核消息,应用层不使用),指定doit消息处理回调函数为demo_echo_cmd,同时指定有效组策略为demo_cmd_policy。
static const struct genl_ops demo_ops[] = {
{
.cmd = DEMO_CMD_ECHO,
.doit = demo_echo_cmd,
.policy = demo_cmd_policy,
.flags = GENL_ADMIN_PERM,
},
};
static const struct nla_policy demo_cmd_policy[DEMO_CMD_ATTR_MAX + 1] = {
[DEMO_CMD_ATTR_MESG] = {.type = NLA_STRING},
[DEMO_CMD_ATTR_DATA] = {.type = NLA_S32},
};
限定DEMO_CMD_ATTR_MESG的属性类型为NLA_STRING(字符串类型),限定DEMO_CMD_ATTR_DATA的属性类型为NLA_S32(有符号32位数)。
调用genl_register_family_with_ops()同时注册demo_family及demo_ops.
向所有的应用层加入CTRL控制器簇组播组的Generic Netlink套接字多播发送CTRL_CMD_NEWFAMILY消息,通知应用层有新的family注册了,这样应用层就可以捕获这一消息。
内核层主要就一个回发函数demo_echo_cmd, 其中入参为sk_buff和genl_info。genl_info是内核解析过的数据结构。该函数会根据属性调用cmd_attr_echo_message 或者cmd_attr_echo_data 。
cmd_attr_echo_message函数先打印应用层输入数据,然后依次调用
demo_prepare_reply、demo_mk_reply和demo_send_reply来向应用层发送信息。
其中函数nlmsg_new位于include/net/netlink.h中
/**
* nlmsg_new - Allocate a new netlink message
* @payload: size of the message payload
* @flags: the type of memory to allocate.
*
* Use NLMSG_DEFAULT_SIZE if the size of the payload isn't known
* and a good default is needed.
*/
static inline struct sk_buff *nlmsg_new(size_t payload, gfp_t flags)
{
return alloc_skb(nlmsg_total_size(payload), flags);
}
内核具体源码如下:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <net/genetlink.h>
#define DEMO_GENL_NAME "DEMO_GEN_CTRL"
#define DEMO_GENL_VERSION 0x1
enum
{
DEMO_CMD_UNSPEC = 0, /* Reserved */
DEMO_CMD_ECHO, /* user->kernel request/get-response */
DEMO_CMD_REPLY, /* kernel->user event */
__DEMO_CMD_MAX,
};
#define DEMO_CMD_MAX (__DEMO_CMD_MAX - 1)
enum
{
DEMO_CMD_ATTR_UNSPEC = 0,
DEMO_CMD_ATTR_MESG, /* demo message */
DEMO_CMD_ATTR_DATA, /* demo data */
__DEMO_CMD_ATTR_MAX,
};
#define DEMO_CMD_ATTR_MAX (__DEMO_CMD_ATTR_MAX - 1)
static struct genl_family demo_family = {
.id = GENL_ID_GENERATE,
.name = DEMO_GENL_NAME,
.version = DEMO_GENL_VERSION,
.maxattr = DEMO_CMD_ATTR_MAX,
};
static const struct nla_policy demo_cmd_policy[DEMO_CMD_ATTR_MAX + 1] = {
[DEMO_CMD_ATTR_MESG] = {.type = NLA_STRING},
[DEMO_CMD_ATTR_DATA] = {.type = NLA_S32},
};
static int
demo_prepare_reply (struct genl_info *info, u8 cmd, struct sk_buff **skbp,
size_t size)
{
struct sk_buff *skb;
void *reply;
skb = genlmsg_new (size, GFP_KERNEL);
if (!skb)
return -ENOMEM;
if (!info)
return -EINVAL;
reply = genlmsg_put_reply (skb, info, &demo_family, 0, cmd);
if (reply == NULL)
{
nlmsg_free (skb);
return -EINVAL;
}
*skbp = skb;
return 0;
}
static int
demo_mk_reply (struct sk_buff *skb, int aggr, void *data, int len)
{
/* add a netlink attribute to a socket buffer */
return nla_put (skb, aggr, len, data);
}
static int
demo_send_reply (struct sk_buff *skb, struct genl_info *info)
{
struct genlmsghdr *genlhdr = nlmsg_data (nlmsg_hdr (skb));
void *reply = genlmsg_data (genlhdr);
genlmsg_end (skb, reply);
return genlmsg_reply (skb, info);
}
static int
cmd_attr_echo_message (struct genl_info *info)
{
struct nlattr *na;
char *msg;
struct sk_buff *rep_skb;
size_t size;
int ret;
na = info->attrs[DEMO_CMD_ATTR_MESG];
if (!na)
return -EINVAL;
msg = (char *) nla_data (na);
pr_info ("demo generic netlink receive echo mesg %s\n", msg);
size = nla_total_size (strlen (msg) + 1);
ret = demo_prepare_reply (info, DEMO_CMD_REPLY, &rep_skb, size);
if (ret < 0)
return ret;
ret = demo_mk_reply (rep_skb, DEMO_CMD_ATTR_MESG, msg, size);
if (ret < 0)
goto err;
return demo_send_reply (rep_skb, info);
err:
nlmsg_free (rep_skb);
return ret;
}
static int
cmd_attr_echo_data (struct genl_info *info)
{
struct nlattr *na;
s32 data;
struct sk_buff *rep_skb;
size_t size;
int ret;
na = info->attrs[DEMO_CMD_ATTR_DATA];
if (!na)
return -EINVAL;
data = nla_get_s32 (info->attrs[DEMO_CMD_ATTR_DATA]);
pr_info ("demo generic netlink receive echo data %d\n", data);
size = nla_total_size (sizeof (s32));
ret = demo_prepare_reply (info, DEMO_CMD_REPLY, &rep_skb, size);
if (ret < 0)
return ret;
ret = nla_put_s32 (rep_skb, DEMO_CMD_ATTR_DATA, data);
if (ret < 0)
goto err;
return demo_send_reply (rep_skb, info);
err:
nlmsg_free (rep_skb);
return ret;
}
static int
demo_echo_cmd (struct sk_buff *skb, struct genl_info *info)
{
if (info->attrs[DEMO_CMD_ATTR_MESG])
return cmd_attr_echo_message (info);
else if (info->attrs[DEMO_CMD_ATTR_DATA])
return cmd_attr_echo_data (info);
else
return -EINVAL;
}
static const struct genl_ops demo_ops[] = {
{
.cmd = DEMO_CMD_ECHO,
.doit = demo_echo_cmd,
.policy = demo_cmd_policy,
.flags = GENL_ADMIN_PERM,
},
};
static int __init
demo_genetlink_init (void)
{
int ret;
pr_info ("demo generic netlink module %d init...\n", DEMO_GENL_VERSION);
ret = genl_register_family_with_ops (&demo_family, demo_ops);
if (ret != 0)
{
pr_info ("failed to init demo generic netlink example module\n");
return ret;
}
pr_info ("demo generic netlink module init success\n");
return 0;
}
static void __exit
demo_genetlink_exit (void)
{
int ret;
printk ("demo generic netlink deinit.\n");
ret = genl_unregister_family (&demo_family);
if (ret != 0)
{
printk ("faled to unregister family:%i\n", ret);
}
}
module_init (demo_genetlink_init);
module_exit (demo_genetlink_exit);
MODULE_LICENSE ("GPL");
其中Makefile如下:
obj-m := genetlink_kern.o
KERNELBUILD := /lib/modules/`uname -r`/build
default:
@echo "BUILE Kmod"
@make -C $(KERNELBUILD) M=$(shell pwd) modules
clean:
@echo " CLEAN kmod"
@rm -rf *.o
@rm -rf .depend .*.cmd *.ko *.mod.c .tmp_versions *.symvers .*.d
1.1.8.2 应用层
通过函数demo_create_nl_socket创建一个netlink套接字,根据协议名字找到ID.
然后调用demo_send_cmd发送一个DEMO_CMD_ECHO类型的字符串消息,继续发送一个数据消息。
最后调用demo_msg_recv_analysis来接收内核发送的数据。
应用层代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <poll.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <signal.h>
#include <linux/genetlink.h>
#define DEMO_GENL_NAME "DEMO_GEN_CTRL"
#define DEMO_GENL_VERSION 0x1
enum
{
DEMO_CMD_UNSPEC = 0, /* Reserved */
DEMO_CMD_ECHO, /* user->kernel request/get-response */
DEMO_CMD_REPLY, /* kernel->user event */
__DEMO_CMD_MAX,
};
#define DEMO_CMD_MAX (__DEMO_CMD_MAX - 1)
enum
{
DEMO_CMD_ATTR_UNSPEC = 0,
DEMO_CMD_ATTR_MESG, /* demo message */
DEMO_CMD_ATTR_DATA, /* demo data */
__DEMO_CMD_ATTR_MAX,
};
#define DEMO_CMD_ATTR_MAX (__DEMO_CMD_ATTR_MAX - 1)
/*
* Generic macros for dealing with netlink sockets. Might be duplicated
* elsewhere. It is recommended that commercial grade applications use
* libnl or libnetlink and use the interfaces provided by the library
*/
#define GENLMSG_DATA(glh) ((void *)(NLMSG_DATA(glh) + GENL_HDRLEN))
#define GENLMSG_PAYLOAD(glh) (NLMSG_PAYLOAD(glh, 0) - GENL_HDRLEN)
#define NLA_DATA(na) ((void *)((char*)(na) + NLA_HDRLEN))
#define NLA_PAYLOAD(len) (len - NLA_HDRLEN)
#define MAX_MSG_SIZE 1024
#define DEBUG 1
#define PRINTF(fmt, arg...) { \
if (DEBUG) { \
printf(fmt, ##arg); \
} \
}
struct msgtemplate {
struct nlmsghdr n;
struct genlmsghdr g;
char buf[MAX_MSG_SIZE];
};
/*
* Create a raw netlink socket and bind
*/
static int demo_create_nl_socket(int protocol)
{
int fd;
struct sockaddr_nl local;
fd = socket(AF_NETLINK, SOCK_RAW, protocol);
if (fd < 0)
return -1;
memset(&local, 0, sizeof(local));
local.nl_family = AF_NETLINK;
local.nl_pid = getpid();
if (bind(fd, (struct sockaddr *) &local, sizeof(local)) < 0)
goto error;
return fd;
error:
close(fd);
return -1;
}
static int demo_send_cmd(int sd, __u16 nlmsg_type, __u32 nlmsg_pid,
__u8 genl_cmd, __u16 nla_type,
void *nla_data, int nla_len)
{
struct nlattr *na;
struct sockaddr_nl nladdr;
int r, buflen;
char *buf;
struct msgtemplate msg;
msg.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
msg.n.nlmsg_type = nlmsg_type;
msg.n.nlmsg_flags = NLM_F_REQUEST;
msg.n.nlmsg_seq = 0;
msg.n.nlmsg_pid = nlmsg_pid;
msg.g.cmd = genl_cmd;
msg.g.version = DEMO_GENL_VERSION;
na = (struct nlattr *) GENLMSG_DATA(&msg);
na->nla_type = nla_type;
na->nla_len = nla_len + 1 + NLA_HDRLEN;
memcpy(NLA_DATA(na), nla_data, nla_len);
msg.n.nlmsg_len += NLMSG_ALIGN(na->nla_len);
buf = (char *) &msg;
buflen = msg.n.nlmsg_len;
memset(&nladdr, 0, sizeof(nladdr));
nladdr.nl_family = AF_NETLINK;
while ((r = sendto(sd, buf, buflen, 0, (struct sockaddr *) &nladdr,
sizeof(nladdr))) < buflen) {
if (r > 0) {
buf += r;
buflen -= r;
} else if (errno != EAGAIN)
return -1;
}
return 0;
}
/*
* Probe the controller in genetlink to find the family id
* for the DEMO_GEN_CTRL family
*/
static int demo_get_family_id(int sd)
{
struct msgtemplate ans;
char name[100];
int id = 0, ret;
struct nlattr *na;
int rep_len;
strcpy(name, DEMO_GENL_NAME);
ret = demo_send_cmd(sd, GENL_ID_CTRL, getpid(), CTRL_CMD_GETFAMILY,
CTRL_ATTR_FAMILY_NAME, (void *)name, strlen(DEMO_GENL_NAME)+1);
if (ret < 0)
return 0;
rep_len = recv(sd, &ans, sizeof(ans), 0);
if (ans.n.nlmsg_type == NLMSG_ERROR || (rep_len < 0) || !NLMSG_OK((&ans.n), rep_len))
return 0;
na = (struct nlattr *) GENLMSG_DATA(&ans);
na = (struct nlattr *) ((char *) na + NLA_ALIGN(na->nla_len));
if (na->nla_type == CTRL_ATTR_FAMILY_ID) {
id = *(__u16 *) NLA_DATA(na);
}
return id;
}
int demo_msg_check(struct msgtemplate msg, int rep_len)
{
if (msg.n.nlmsg_type == NLMSG_ERROR || !NLMSG_OK((&msg.n), rep_len)) {
struct nlmsgerr *err = NLMSG_DATA(&msg);
fprintf(stderr, "fatal reply error, errno %d\n", err->error);
return -1;
}
return 0;
}
void demo_msg_recv_analysis(int sd, int num)
{
int rep_len;
int len;
struct nlattr *na;
struct msgtemplate msg;
unsigned int data;
char *string;
while (num--) {
rep_len = recv(sd, &msg, sizeof(msg), 0);
if (rep_len < 0 || demo_msg_check(msg, rep_len) < 0) {
fprintf(stderr, "nonfatal reply error: errno %d\n", errno);
continue;
}
PRINTF("received %d bytes\n", rep_len);
PRINTF("nlmsghdr size=%zu, nlmsg_len=%d, rep_len=%d\n",
sizeof(struct nlmsghdr), msg.n.nlmsg_len, rep_len);
rep_len = GENLMSG_PAYLOAD(&msg.n);
na = (struct nlattr *) GENLMSG_DATA(&msg);
len = 0;
while (len < rep_len) {
len += NLA_ALIGN(na->nla_len);
switch (na->nla_type) {
case DEMO_CMD_ATTR_MESG:
string = (char *) NLA_DATA(na);
printf("echo reply:%s\n", string);
break;
case DEMO_CMD_ATTR_DATA:
data = *(int *) NLA_DATA(na);
printf("echo reply:%u\n", data);
break;
default:
fprintf(stderr, "Unknown nla_type %d\n", na->nla_type);
}
na = (struct nlattr *) (GENLMSG_DATA(&msg) + len);
}
}
}
int main(int argc, char* argv[])
{
int nl_fd;
int nl_family_id;
int my_pid;
int ret;
int data;
char *string;
if (argc < 3) {
printf("invalid input! usage: ./name <char msg> <uint data>\n");
return 0;
}
nl_fd = demo_create_nl_socket(NETLINK_GENERIC);
if (nl_fd < 0) {
fprintf(stderr, "failed to create netlink socket\n");
return 0;
}
nl_family_id = demo_get_family_id(nl_fd);
if (!nl_family_id) {
fprintf(stderr, "Error getting family id, errno %d\n", errno);
goto out;
}
PRINTF("family id %d\n", nl_family_id);
my_pid = getpid();
string = argv[1];
data = atoi(argv[2]);
ret = demo_send_cmd(nl_fd, nl_family_id, my_pid, DEMO_CMD_ECHO,
DEMO_CMD_ATTR_MESG, string, strlen(string) + 1);
if (ret < 0) {
fprintf(stderr, "failed to send echo cmd\n");
goto out;
}
ret = demo_send_cmd(nl_fd, nl_family_id, my_pid, DEMO_CMD_ECHO,
DEMO_CMD_ATTR_DATA, &data, sizeof(data));
if (ret < 0) {
fprintf(stderr, "failed to send echo cmd\n");
goto out;
}
demo_msg_recv_analysis(nl_fd, argc-1);
out:
close(nl_fd);
return 0;
}
1.1.9 参考