Linuxnetlink详解
熟悉Linux wifi的同学都知道,wpa_supplicant程序是基于netlink与wifi驱动进行通信的。 (wpa_supplicant是wifi station用户空间守护进程)
本文学习下Linux的netlink,给出用户空间与内核空间基于netlink通信的示例。 示例包括netlink和generic netlink。用户空间程序包括基于原生Linux API和基于libnl API。 netlink基础
netlink协议是一个基于socket的,用于内核与用户空间进程通信的一个协议。
用户态创建netlink socket的代码如下: int fd = socket(AF_NETLINK, SOCK_RAW, MY_NETLINK) //socket接口的原型为: int socket(int domain, int type, int protocol); //这里用到了第三个入参protocol,即创建Netlink socket时要指定协议号
内核态创建netlink socket的接口原型为: struct sock * netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg) //这里的第二个参数unit为协议号,与用户空间的protocol相同
Netlink消息有固定的格式,struct 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 port ID */ };
完整示例见github: https://github.com/jian-soft/netlink_examples
下文为关键代码注释。 原生Linux API示例内核侧示例//定义自己的netlink协议号 #define MY_NETLINK 31 //接收回调,即内核侧收到用户发来的netlink消息回调 static void netlink_recv_msg(struct sk_buff *skb) { ... nlh = (struct nlmsghdr *)skb->data; //取netlink消息头 pid = nlh->nlmsg_pid; /* pid of sending process */ msg = (char *)nlmsg_data(nlh); //取netlink消息data部分 msg_size = strlen(msg); printk(KERN_INFO "netlink_kernel: Received from pid %d: %s ", pid, msg); ... } //定义netlink_kernel_cfg,即声明接收回调 struct netlink_kernel_cfg cfg = { .input = netlink_recv_msg, }; //创建内核测netlink socket g_nl_sock = netlink_kernel_create(&init_net, MY_NETLINK, &cfg); if (!g_nl_sock) { printk(KERN_ALERT "netlink_kernel: Error creating socket. "); return -10; } 用户侧示例int main(int argc, char* argv[]) { //创建socket sock_fd = socket(PF_NETLINK, SOCK_RAW, MY_NETLINK); memset(&src_addr, 0, sizeof(src_addr)); src_addr.nl_family = AF_NETLINK; src_addr.nl_pid = getpid(); /* self pid */ //绑定端口 bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr)); //设置目标地址为内核netlink 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 */ //可以通过sendmsg或sendto两种接口向内核发送消息 //相应的有recvmsg和recvfrom两种接口接收来自内核的消息 //详见github,此处略 } libnl API示例
libnl 对用户空间Linu原生的netlink API进行了封装,使得用户空间程序更容易编写,尤其是对于generic netlink API。 关于generic netlink API我们下一章节详细介绍。
另外wpa_supplicant与内核wifi驱动的通信就是用的libnl generic netlink API。
先介绍下libnl的主要接口,定义在头文件 //创建netlink socket, libnl中用struct nl_sock表示一个socket #include struct nl_sock *nl_socket_alloc(void) void nl_socket_free(struct nl_sock *sk) //回调配置 struct nl_cb *nl_socket_get_cb(const struct nl_sock *sk); void nl_socket_set_cb(struct nl_sock *sk, struct nl_cb *cb); int nl_socket_modify_cb(struct nl_sock *, enum nl_cb_type, enum nl_cb_kind, nl_recvmsg_msg_cb_t, void *); //发送 int nl_send_auto(struct nl_sock *sk, struct nl_msg *msg) int nl_send(struct nl_sock *sk, struct nl_msg *msg) int nl_send_iovec(struct nl_sock *sk, struct nl_msg *msg, struct iovec *iov, unsigned iovlen) int nl_sendmsg(struct nl_sock *sk, struct nl_msg *msg, struct msghdr *hdr) //nl_sendmsg里调用Linux原生sendmsg接口 int nl_sendto(struct nl_sock *sk, void *buf, size_t size) //nl_sendto调用Linux原生sendto接口 int nl_send_simple(struct nl_sock *sk, int type, int flags, void *buf, size_t size) //接收 int nl_recvmsgs_default(struct nl_sock *sk) int nl_recvmsgs(struct nl_sock *sk, struct nl_cb *cb) //如果socket是阻塞的,就阻塞式接收。recv到数据之后,通过cb进行处理 libnl用户侧示例
基于上一节的例子,内核测代码不变,用户侧使用libnl重写。 #include #include #define MY_NETLINK 31 #define MY_NETLINK_TYPE_SET 0 //接收回调 static int my_input(struct nl_msg *msg, void *arg) { struct nlmsghdr *nlh = nlmsg_hdr(msg); char *data = nlmsg_data(nlh); int datalen = nlmsg_datalen(nlh); printf("input cb: datalen:%d, data:%d ", datalen, data); return 0; } int main(int argc, char* argv[]) { struct nl_sock *sk; int ret; //创建并绑定socket sk = nl_socket_alloc(); ret = nl_connect(sk, MY_NETLINK); //修改接收回调函数,收到任何消息都会回调my_input nl_socket_modify_cb(sk, NL_CB_MSG_IN, NL_CB_CUSTOM, my_input, NULL); char msg[] = "Hello libnl! " ret = nl_send_simple(sk, MY_NETLINK_TYPE_SET, 0, msg, sizeof(msg)); //阻塞式等待接收。接收到内核发来的消息后,会进入接收回调my_input nl_recvmsgs_default(sk); nl_socket_free(sk); } generic netlink示例,基于libnl
netlink通信协议在不修改内核源码的情况下,最大只支持定义32种协议。 随着netlink的使用越来越多,32个协议号已不够用,所以引入了generic netlink。 generic netlink其实是对netlink报文进行了又一次封装,generic netlink使用的netlink协议号是NETLINK_GENERIC=16。
genl的消息格式如下: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Netlink message header (nlmsghdr) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Generic Netlink message header (genlmsghdr) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Optional user specific message header | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Optional Generic Netlink message payload | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ struct genlmsghdr { __u8 cmd; __u8 version; __u16 reserved; }; //genl的message payload基于netlink的属性机制,即payload是由一个个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; }; genl内核侧示例
参考: https://wiki.linuxfoundation.org/networking/generic_netlink_howto
注意:genl_register_ops接口只在3.12及之前版本有; 3.13~4.9版本用genl_register_family_with_ops; 4.10版本及以后没有注册ops的接口,只有注册family的接口,ops要直接定义在family内。
本文示例基于4.15内核。
注册generic netlink family需要3步: 定义操作 定义family 注册family /* Step1: 定义操作 */ /* attributes */ enum { EXMPL_A_UNSPEC, EXMPL_A_MSG, _EXMPL_A_MAX, }; #define EXMPL_A_MAX (_EXMPL_A_MAX - 1) /* attribute policy */ static struct nla_policy exmpl_genl_policy[EXMPL_A_MAX + 1] = { [EXMPL_A_MSG] = {.type = NLA_NUL_STRING}, }; // handler static int exmpl_echo(struct sk_buff *skb, struct genl_info *info); // commands enum { EXMPL_C_UNSPEC, EXMPL_C_ECHO, _EXMPL_C_MAX, }; #define EXMPL_C_MAX (_EXMPL_C_MAX - 1) // operation definition struct genl_ops exmpl_genl_ops[EXMPL_C_MAX] = { { .cmd = EXMPL_C_ECHO, .doit = exmpl_echo, .policy = exmpl_genl_policy, } }; #define FAMILY_NAME "my_genl" /* Step2: 定义family */ // family definition static struct genl_family my_genl_family = { .id = 0, .hdrsize = 0, //表示没有用户自定义的额外header .name = FAMILY_NAME, .version = 1, .ops = exmpl_genl_ops, .n_ops = ARRAY_SIZE(exmpl_genl_ops), .maxattr = EXMPL_A_MAX + 1, }; // handler的具体定义 static int exmpl_echo(struct sk_buff *skb, struct genl_info *info) { struct nlattr *na; struct sk_buff *reply_skb; void *msg_head; int ret; printk("%s in. ", __func__); //内核已经解析好了每个attr na = info->attrs[EXMPL_A_MSG]; if (!na) { printk("Error: attr EXMPL_A_MSG is null "); return -EINVAL; } printk("Recv message: %s ", nla_data(na)); //将收到的消息发回去 reply_skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); //填写genl消息头 msg_head = genlmsg_put(reply_skb, info->snd_portid, info->snd_seq, &my_genl_family, 0, EXMPL_C_ECHO); //向skb尾部填写attr nla_put_string(reply_skb, EXMPL_A_MSG, nla_data(na)); //Finalize the message: 更新nlmsghdr中的nlmsg_len字段 genlmsg_end(reply_skb, msg_head); //Send the message back ret = genlmsg_reply(reply_skb, info); if (ret != 0) { printk("genlmsg_reply return fail: %d ", ret); return -ret; } return 0; } /* Step3: 注册famliy */ int ret; ret = genl_register_family(&my_genl_family); if (err != 0) { printk("genl_register_family fail, ret:%d ", ret); return ret; } genl用户侧示例(基于libnl)#define MY_FAMILY_NAME "my_genl" //用户侧需要定义和内核侧相同的属性以及命令,所以通常把这一部分摘成一个独立的.h,内核和app共用 //这里没有摘成一个独立的.h,用户侧也重复定义一份 /* attributes */ enum { EXMPL_A_UNSPEC, EXMPL_A_MSG, _EXMPL_A_MAX, }; #define EXMPL_A_MAX (_EXMPL_A_MAX - 1) // define attribute policy static struct nla_policy exmpl_genl_policy[EXMPL_A_MAX + 1] = { [EXMPL_A_MSG] = {.type = NLA_STRING}, }; // commands enum { EXMPL_C_UNSPEC, EXMPL_C_ECHO, _EXMPL_C_MAX, }; #define EXMPL_C_MAX (_EXMPL_C_MAX - 1) //接收回调定义 int recv_callback(struct nl_msg* recv_msg, void* arg) { struct nlmsghdr *nlh = nlmsg_hdr(recv_msg); struct nlattr *tb_msg[EXMPL_A_MAX + 1]; if (nlh->nlmsg_type == NLMSG_ERROR) { printf("Received NLMSG_ERROR message! "); return NL_STOP; } struct genlmsghdr *gnlh = (struct genlmsghdr*)nlmsg_data(nlh); //按照每attr解析内核发来的genl消息 nla_parse(tb_msg, EXMPL_A_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), exmpl_genl_policy); //判断是否包含属性EXMPL_A_MSG if (tb_msg[EXMPL_A_MSG]) { // parse it as string char * payload_msg = nla_get_string(tb_msg[EXMPL_A_MSG]); printf("Kernel replied: %s ", payload_msg); } else { printf("Attribute EXMPL_A_MSG is missing "); } return NL_OK; } int main(int argc, char* argv[]) { //创建并连接genl socket struct nl_sock *sk = nl_socket_alloc(); genl_connect(sk); //根据FAMILY_NAME获得对应的famlily_id int family_id; family_id = genl_ctrl_resolve(sk, FAMILY_NAME); if (family_id < 0) { printf("generic netlink family "" FAMILY_NAME "" NOT REGISTERED "); nl_socket_free(sk); exit(-1); } else { printf("Family-ID of generic netlink family "" FAMILY_NAME "" is: %d ", family_id); } //设置接收回调 nl_socket_modify_cb(sk, NL_CB_MSG_IN, NL_CB_CUSTOM, recv_callback, NULL); //发送消息 struct nl_msg *msg = nlmsg_alloc(); genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, family_id, 0, NLM_F_REQUEST, EXMPL_C_ECHO, 1); NLA_PUT_STRING(msg, EXMPL_A_MSG, "genl message from user to kernel"); int res = nl_send_auto(sk, msg); nlmsg_free(msg); if (res < 0) { printf("nl_send_auto fail, ret:%d ", res); } else { printf("nl_send_auto OK, ret: %d ", res); } //接收消息。接收到内核发来的消息后,触发回调recv_callback nl_recvmsgs_default(sk); nla_put_failure: //referenced by NLA_PUT_STRING nl_socket_free(sk); return 0; }
这里的示例是内核收到用户空间发来的genl消息后,根据发送端的struct genl_info *info, 调用genlmsg_reply(reply_skb, info),将内核的genl消息单播给用户空间app。
如果内核不知道用户空间的socket信息,内核如何将消息发送到用户空间呢? 这时一般用组播netlink消息,即内核将消息组播出去。用户空间谁订阅了这个组播,谁就能收到内核发来的消息。 关于组播netlink示例,后续有空再补一下…
完整示例见github: https://github.com/jian-soft/netlink_examples
iPhone14的颜值高于iPhone13,消费者该不该等?在iPhone13正式来临之前,知名爆料人悄悄带来了iPhone14的消息,同时放出了一组iPhone14系列的外观设计渲染图。至于FaceID,目前尚不清楚苹果会采用屏下方案,还
谷歌第一次用上12GB内存?Pixel6Pro这款产品的消息很多的小伙伴都是比较的漠然的吧,不少的人都是比较的好奇这款产品的消息的吧,那么我们一起来看看最新的一个信息吧!说不定还有惊喜呢!据悉,Geekben
华为鸿蒙电池健康系统,正分批推送上线华为华为鸿蒙华为鸿蒙系统华为鸿蒙系统最近在HarmonyOS2。0。0。168版本,推送电池健康系统,正在分批推送上线,在电池更多设置选项中出现了一项最大容量,系统可评估设备的当前
ColorOS12官宣本月16日晚发布,OPPO诸多新品一同亮相今天,ColorOS官方微博宣布,9月16日晚上1800正式发布全新ColorOS12。据目前所了解的信息来看,这次新UI主要在设计美学,系统流畅性,协同交互上有诸多亮点。从官宣海
超越时代,九项全能南卡N2s耳机体验现代社会生活不易,芸芸众生皆为生活奔波,不知道有多少人和我一样,每天只有在上下班的路上,才有真正只属于自己的时间。这时候,不论听听音乐看看电视剧,都是非常惬意的事情。感谢ZAKER
平时只用流量,有什么便宜的流量套餐吗?移动9元月,45G流量,1300分钟通话。联通18元月,90G流量,1500分钟通话。移动套餐我这个套餐是天津的,今年年初办的,原来用的联通冰淇淋套餐,不过今年感觉流量不够用,才换
除了直通车流量,淘宝店铺没有一个自然流量怎么做?十余年电商从业者,分享我的见解经验技巧。我用心写,你用心学!除了直通车流量,淘宝店铺没有一个自然流量?这就是老花为什么一直跟大家讲,不建议盲目开车!你的店铺没有没有数据,即使是用了
比较靠谱好用的无限流量卡有哪些,可以是纯流量卡?谢邀!现在已经没有无限流量卡了,从10月份开始,三大运营商的新套餐都是达量不限速套餐,也就是在用完套餐内流量之后,如果继续使用流量则不会再限速,但是会额外收费,通常是1元3GB的价
快手电商想要二次生长,底气何在?丨氪金lite从4月开始品牌自播,到8月O粉节超品日活动总裁直播单场GMV超1000万总GMV超8000万,OPPO在快手电商生态的发展速度超出许多人的想象。这4个月,我们在快手从10万粉丝的O
三星s22系列配置曝光,看起来散热和拍照都有明显升级?三星s22消息曝光了!这次看起来应该会有新的升级,不过对于这款产品,这次可能有一些升级值得关注!现在曝光的是三星s22plus和ultra!这两款应该都是旗舰产品!当然使用的芯片会
放开微信外链,腾讯无法管理?多虑了本报讯(记者田冲)关于互联网互联互通的话题,最近引发了不少关注。从监管动作来看,互联互通势在必行,微信淘宝抖音不能再互相屏蔽外链。据媒体报道称,9月9日,工信部有关业务部门召开了屏