netlink的使用方法
更新时间:2023-05-28 14:04:36 阅读量: 实用文档 文档下载
- netlink的作用推荐度:
- 相关推荐
netlink的使用方法
《netlink的使用方法》
在 Linux 2.4 版以后版本的内核中,几乎全部的中断过程与用户态进程的通信都是使用netlink 套接字实现的,著名的内核包过滤框架Netfilter 在与用户空间的通读,也在最新版本中改变为netlink,无疑,它将是Linux 用户态与内核态交流的主要方法之一。它的通信依据是一个对应于进程的标识,一般定为该进程的 ID。当通信的一端处于中断过程时,该标识为 0。当使用netlink 套接字进行通信,通信的双方都是用户态进程,则使用方法类似于消息队列。netlink 套接字的最大特点是对中断过程的支持,它在内核空间接收用户空间数据时不再需要用户自行启动一个内核线程,而是通过另一个软中断调用用户事先指定的接收函数。这样就可以保证数据接收的实时性。
当 netlink 套接字用于内核空间与用户空间的通信时,在用户空间的创建方法和一般套接字使用类似,但内核空间的创建方法则不同。
用户空间
用户态应用使用标准的socket 与内核通讯,标准的socket API 的函数, socket(), bind(),sendmsg(), recvmsg() 和 close()很容易地应用到 netlink socket。
为了创建一个 netlink socket,用户需要使用如下参数调用 socket():
socket(AF_NETLINK, SOCK_RAW, netlink_type)
netlink 对应的协议簇是 AF_NETLINK,第二个参数必须是SOCK_RAW 或SOCK_DGRAM,第三个参数指定netlink 协议类型,它可以是一个自定义的类型,也可以使用内核预定义的类型: #define NETLINK_ROUTE 0 /* Routing/device hook */
#define NETLINK_W1 1 /* 1-wire subsystem */
#define NETLINK_USERSOCK 2 /* Reserved for user mode socket protocols */
#define NETLINK_FIREWALL 3 /* Firewalling hook */
#define NETLINK_INET_DIAG 4 /* INET socket monitoring */
#define NETLINK_NFLOG 5 /* netfilter/iptables ULOG */
#define NETLINK_XFRM 6 /* ipsec */
#define NETLINK_SELINUX 7 /* SELinux event notifications */
#define NETLINK_ISCSI 8 /* Open-iSCSI */
#define NETLINK_AUDIT 9 /* auditing */
#define NETLINK_FIB_LOOKUP 10
#define NETLINK_CONNECTOR 11
#define NETLINK_NETFILTER 12 /* netfilter subsystem */
#define NETLINK_IP6_FW 13
#define NETLINK_DNRTMSG 14 /* DECnet routing messages */
#define NETLINK_KOBJECT_UEVENT 15 /* Kernel messages to userspace */
#define NETLINK_GENERIC 16
同样地,socket 函数返回的套接字,可以交给bind等函数调用:
static int skfd;
skfd = socket(PF_NETLINK, SOCK_RAW, NL_IMP2);
if(skfd < 0)
{
printf("can not create a netlink socket\n");
exit(0);
}
bind 函数需要绑定协议地址,netlink 的socket 地址使用struct sockaddr_nl 结构描述:
struct sockaddr_nl
{
sa_family_t nl_family;
unsigned short nl_pad;
__u32 nl_pid;
netlink的使用方法
__u32 nl_groups;
};
成员 nl_family 为协议簇 AF_NETLINK,成员 nl_pad 当前没有使用,因此要总是设置为 0,成员 nl_pid 为接收或发送消息的进程的 ID,如果希望内核处理消息或多播消息,就把该字段设置为 0,否则设置为处理消息的进程 ID。成员 nl_groups 用于指定多播组,bind 函数用于把调用进程加入到该字段指定的多播组,如果设置为 0,表示调用者不加入任何多播组:
struct sockaddr_nl local;
memset(&local, 0, sizeof(local));
local.nl_family = AF_NETLINK;
local.nl_pid = getpid(); /*设置pid 为自己的pid 值*/
local.nl_groups = 0;
if(bind(skfd, (struct sockaddr*)&local, sizeof(local)) != 0) /*绑定套接字*/
{
printf("bind() error\n");
return -1;
}
用户空间可以调用send 函数簇向内核发送消息,如sendto、sendmsg 等,同样地,也可以使用struct sockaddr_nl 来描述一个对端地址,以待send 函数来调用,与本地地址稍不同的是,因为对端为内核,所以nl_pid 成员需要设置为0:
struct sockaddr_nl kpeer;
memset(&kpeer, 0, sizeof(kpeer));
kpeer.nl_family = AF_NETLINK;
kpeer.nl_pid = 0;
kpeer.nl_groups = 0;
另一个问题就是发内核发送的消息的组成,使用我们发送一个IP 网络数据包的话,则数据包结构为“IP 包头+IP 数据”,同样地,netlink 的消息结构是“netlink 消息头部+数据”。Netlink 消息头部使用struct nlmsghdr 结构来描述:
struct nlmsghdr
{
__u32 nlmsg_len; /* Length of message */
__u16 nlmsg_type; /* Message type*/
__u16 nlmsg_flags; /* Additional flags */
__u32 nlmsg_seq; /* Sequence number */
__u32 nlmsg_pid; /* Sending process PID */
};
字段 nlmsg_len 指定消息的总长度,包括紧跟该结构的数据部分长度以及该结构的大小,一般地,我们使用netlink 提供的宏NLMSG_LENGTH 来计算这个长度,仅需向NLMSG_LENGTH宏提供要发送的数据的长度,它会自动计算对齐后的总长度:
/*计算包含报头的数据报长度*/
#define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(sizeof(struct nlmsghdr)))
/*字节对齐*/
#define NLMSG_ALIGN(len) (((len)+NLMSG_ALIGNTO-1)& ~(NLMSG_ALIGNTO-1) )
后面还可以看到很多netlink 提供的宏,这些宏可以为我们编写netlink 宏提供很大的方便。
字段 nlmsg_type 用于应用内部定义消息的类型,它对 netlink 内核实现是透明的,因此大部分情况下设置为 0,字段 nlmsg_flags 用于设置消息标志,对于一般的使用,用户把它设置为 0 就可以,只是一些高级应用(如 netfilter 和路由 daemon 需要它进行一些复杂的操作),字段nlmsg_seq 和 nlmsg_pid 用于应用追踪消息,前者表示顺序号,后者为消息来源进程 ID。
struct msg_to_kernel /*自定义消息首部,它仅包含了netlink 的消息首部*/
{
struct nlmsghdr hdr;
netlink的使用方法
};
struct msg_to_kernel message;
memset(&message, 0, sizeof(message));
message.hdr.nlmsg_len = NLMSG_LENGTH(0); /*计算消息,因为这里只是发送一个请求消息,没
有多余的数据,所以,数据长度为0*/
message.hdr.nlmsg_flags = 0;
message.hdr.nlmsg_type = IMP2_U_PID; /*设置自定义消息类型*/
message.hdr.nlmsg_pid = local.nl_pid; /*设置发送者的PID*/
这样,有了本地地址、对端地址和发送的数据,就可以调用发送函数将消息发送给内核了:
sendto(skfd, &message, message.hdr.nlmsg_len, 0, /*发送一个请求*/
(struct sockaddr*)&kpeer, sizeof(kpeer));
当发送完请求后,就可以调用recv 函数簇从内核接收数据了,接收到的数据包含了netlink
消息首部和要传输的数据:
/*接收的数据包含了netlink 消息首部和自定义数据结构*/
struct u_packet_info
{
struct nlmsghdr hdr;
struct packet_info icmp_info;
};
struct u_packet_info info;
while(1)
{
kpeerlen = sizeof(struct sockaddr_nl);
rcvlen = recvfrom(skfd, &info, sizeof(struct u_packet_info),0, (struct sockaddr*)&kpeer, &kpeerlen); /*接收内核空间返回的数据;处理接收到的数据*/
}
同样地,函数close 用于关闭打开的netlink socket。程序中,因为程序一直循环接收处理内核的消息,需要收到用户的关闭信号才会退出,所以关闭套接字的工作放在了自定义的信号函数sig_int 中处理: /*这个信号函数,处理一些程序退出时的动作*/
static void sig_int(int signo)
{
struct sockaddr_nl kpeer;
struct msg_to_kernel message;
memset(&kpeer, 0, sizeof(kpeer));
kpeer.nl_family = AF_NETLINK;
kpeer.nl_pid = 0;
kpeer.nl_groups = 0;
memset(&message, 0, sizeof(message));
message.hdr.nlmsg_len = NLMSG_LENGTH(0);
message.hdr.nlmsg_flags = 0;
message.hdr.nlmsg_type = IMP2_CLOSE;
message.hdr.nlmsg_pid = getpid();
/*向内核发送一个消息,由nlmsg_type 表明,应用程序将关闭*/
sendto(skfd, &message, message.hdr.nlmsg_len, 0, (struct sockaddr *)(&kpeer), sizeof(kpeer));
close(skfd);
exit(0);
}
这个结束函数中,向内核发送一个“我已经退出了”的消息,然后调用close 函数关闭netlink套接字,退出程序。
netlink的使用方法
内核空间
与应用程序内核,内核空间也主要完成三件工作:
n 创建netlink 套接字
n 接收处理用户空间发送的数据
n 发送数据至用户空间
API 函数netlink_kernel_create 用于创建一个netlink socket,同时,注册一个回调函数,用于接收处理用户空间的消息:
struct sock * netlink_kernel_create(int unit, void (*input)(struct sock *sk, int len));
参数unit 表示netlink 协议类型,如NL_IMP2,参数input 则为内核模块定义的netlink 消息处理函数,当有消息到达这个netlink socket 时,该input 函数指针就会被引用。函数指针input 的参数sk 实际上就是函数netlink_kernel_create 返回的struct sock 指针,sock 实际是socket 的一个内核表示数据结构,用户态应用创建的socket 在内核中也会有一个struct sock 结构来表示。
static int __init init(void)
{
rwlock_init(&user_proc.lock); /*初始化读写锁*/
/*创建一个netlink socket,协议类型是自定义的ML_IMP2,kernel_reveive 为接受处理函数*/ nlfd = netlink_kernel_create(NL_IMP2, kernel_receive);
if(!nlfd) /*创建失败*/
{
printk("can not create a netlink socket\n");
return -1;
}
return nf_register_hook(&imp2_ops); /*注册一个Netfilter 钩子*/
}
module_init(init);
用户空间向内核发送了两种自定义消息类型:IMP2_U_PID 和IMP2_CLOSE,分别是请求和关闭。kernel_receive 函数分别处理这两种消息:
DECLARE_MUTEX(receive_sem); /*初始化信号量*/
static void kernel_receive(struct sock *sk, int len)
{
do
{
struct sk_buff *skb;
if(down_trylock(&receive_sem)) /*获取信号量*/
return;
/*从接收队列中取得skb,然后进行一些基本的长度的合法性校验*/
while((skb = skb_dequeue(&sk->receive_queue)) != NULL)
{
{
struct nlmsghdr *nlh = NULL;
if(skb->len >= sizeof(struct nlmsghdr))
{/*获取数据中的nlmsghdr 结构的报头*/
nlh = (struct nlmsghdr *)skb->data;
if((nlh->nlmsg_len >= sizeof(struct nlmsghdr))&& (skb->len >= nlh->nlmsg_len))
{ /*长度的全法性校验完成后,处理应用程序自定义消息类型,主要是对用户PID
的保存,即为内核保存“把消息发送给谁”*/
if(nlh->nlmsg_type == IMP2_U_PID) /*请求*/
{
write_lock_bh(&user_proc.pid);
netlink的使用方法
user_proc.pid = nlh->nlmsg_pid;
write_unlock_bh(&user_proc.pid);
}
else if(nlh->nlmsg_type == IMP2_CLOSE) /*应用程序关闭*/
{
write_lock_bh(&user_proc.pid);
if(nlh->nlmsg_pid == user_proc.pid)
user_proc.pid = 0;
write_unlock_bh(&user_proc.pid);
}
}
}
}
kfree_skb(skb);
}
up(&receive_sem); /*返回信号量*/
}while(nlfd && nlfd->receive_queue.qlen);
}
因为内核模块可能同时被多个进程同时调用,所以函数中使用了信号量和锁来进行互斥。
skb = skb_dequeue(&sk->receive_queue)用于取得socket sk 的接收队列上的消息,返回为一个struct sk_buff 的结构,skb->data 指向实际的netlink 消息。
程序中注册了一个Netfilter 钩子,钩子函数是get_icmp,它截获ICMP 数据包,然后调用send_to_user 函数将数据发送给应用空间进程。发送的数据是info 结构变量,它是struct packet_info结构,这个结构包含了来源/目的地址两个成员。Netfilter Hook 不是本文描述的重点,略过。
send_to_user 用于将数据发送给用户空间进程,发送调用的是API 函数netlink_unicast 完成的: int netlink_unicast(struct sock *sk, struct sk_buff *skb, u32 pid, int nonblock);
参数sk 为函数netlink_kernel_create()返回的套接字,参数skb 存放待发送的消息,它的data字段指向要发送的netlink 消息结构,而skb 的控制块保存了消息的地址信息, 参数pid 为接收消息进程的pid,参数nonblock 表示该函数是否为非阻塞,如果为1,该函数将在没有接收缓存可利用时立即返回,而如果为0,该函数在没有接收缓存可利用时睡眠。
向用户空间进程发送的消息包含三个部份:netlink 消息头部、数据部份和控制字段,控制字段包含了内核发送netlink 消息时,需要设置的目标地址与源地址,内核中消息是通过sk_buff 来管理的, linux/netlink.h 中定义了NETLINK_CB 宏来方便消息的地址设置:
#define NETLINK_CB(skb) (*(struct netlink_skb_parms*)&((skb)->cb))
例如:
NETLINK_CB(skb).pid = 0;
NETLINK_CB(skb).dst_pid = 0;
NETLINK_CB(skb).dst_group = 1;
字段pid 表示消息发送者进程ID,也即源地址,对于内核,它为 0, dst_pid 表示消息接收者进程 ID,也即目标地址,如果目标为组或内核,它设置为 0,否则 dst_group 表示目标组地址,如果它目标为某一进程或内核,dst_group 应当设置为 0。
static int send_to_user(struct packet_info *info)
{
int ret;
int size;
unsigned char *old_tail;
struct sk_buff *skb;
struct nlmsghdr *nlh;
struct packet_info *packet;
size = NLMSG_SPACE(sizeof(*info)); /*计算消息总长:消息首部加上数据加度*/
netlink的使用方法
skb = alloc_skb(size, GFP_ATOMIC); /*分配一个新的套接字缓存*/
old_tail = skb->tail;
nlh = NLMSG_PUT(skb, 0, 0, IMP2_K_MSG, size-sizeof(*nlh)); /*初始化一个netlink 消息首部*/ packet = NLMSG_DATA(nlh); /*跳过消息首部,指向数据区*/
memset(packet, 0, sizeof(struct packet_info)); /*初始化数据区*/
packet->src = info->src; /*填充待发送的数据*/
packet->dest = info->dest;
nlh->nlmsg_len = skb->tail - old_tail; /*计算skb 两次长度之差,即netlink 的长度总和*/
NETLINK_CB(skb).dst_groups = 0; /*设置控制字段*/
read_lock_bh(&user_proc.lock); /*发送数据*/
ret = netlink_unicast(nlfd, skb, user_proc.pid, MSG_DONTWAIT);
read_unlock_bh(&user_proc.lock);
}
函数初始化netlink 消息首部,填充数据区,然后设置控制字段,这三部份都包含在skb_buff中,最后调用netlink_unicast 函数把数据发送出去。
函数中调用了netlink 的一个重要的宏NLMSG_PUT,它用于初始化netlink 消息首部:
#define NLMSG_PUT(skb, pid, seq, type, len) \
({ if (skb_tailroom(skb) < (int)NLMSG_SPACE(len)) goto nlmsg_failure; \
__nlmsg_put(skb, pid, seq, type, len); })
static __inline__ struct nlmsghdr *
__nlmsg_put(struct sk_buff *skb, u32 pid, u32 seq, int type, int len)
{
struct nlmsghdr *nlh;
int size = NLMSG_LENGTH(len);
nlh = (struct nlmsghdr*)skb_put(skb, NLMSG_ALIGN(size));
nlh->nlmsg_type = type;
nlh->nlmsg_len = size;
nlh->nlmsg_flags = 0;
nlh->nlmsg_pid = pid;
nlh->nlmsg_seq = seq;
return nlh;
}
这个宏一个需要注意的地方是调用了nlmsg_failure 标签,所以在程序中应该定义这个标签。在内核中使用函数sock_release 来释放函数netlink_kernel_create()创建的netlink socket:
void sock_release(struct socket * sock);
程序在退出模块中释放netlink sockets 和netfilter hook:
static void __exit fini(void)
{
if(nlfd)
{
sock_release(nlfd->socket); /*释放netlink socket*/
}
nf_unregister_hook(&imp2_ops); /*撤锁netfilter 钩子*/}
}
正在阅读:
netlink的使用方法05-28
工程竣工结算管理制度05-04
侠客英雄传ios版攻略03-29
2014届高考语文一轮复习 考点针对训练 文言翻译与断句 新人教版05-20
PPP与地方债解决方案03-31
浅析城市道路工程测量质量的有效控制04-13
关于***市城市管理工作的调研报告 范文02-23
信封娃娃教案03-18
参公考试模拟考试题及答案05-06
2020年幼儿园教研组工作计划方案范文05-09
- 教学能力大赛决赛获奖-教学实施报告-(完整图文版)
- 互联网+数据中心行业分析报告
- 2017上海杨浦区高三一模数学试题及答案
- 招商部差旅接待管理制度(4-25)
- 学生游玩安全注意事项
- 学生信息管理系统(文档模板供参考)
- 叉车门架有限元分析及系统设计
- 2014帮助残疾人志愿者服务情况记录
- 叶绿体中色素的提取和分离实验
- 中国食物成分表2020年最新权威完整改进版
- 推动国土资源领域生态文明建设
- 给水管道冲洗和消毒记录
- 计算机软件专业自我评价
- 高中数学必修1-5知识点归纳
- 2018-2022年中国第五代移动通信技术(5G)产业深度分析及发展前景研究报告发展趋势(目录)
- 生产车间巡查制度
- 2018版中国光热发电行业深度研究报告目录
- (通用)2019年中考数学总复习 第一章 第四节 数的开方与二次根式课件
- 2017_2018学年高中语文第二单元第4课说数课件粤教版
- 上市新药Lumateperone(卢美哌隆)合成检索总结报告
- 使用方法
- netlink
- 农民工城市融入问题探究
- 2013-2018年中国手表市场竞争及投资策略研究报告
- 测控电路课后答案
- 重大活动人员职责分工表
- 训练孩子注意力集中的方法 2
- 山东省济宁市高中政治 历史观哲学讲义 新人教版必修4
- 小升初语文讲义第1讲--拼音
- 2011--2012学年度第一学期期末综合题 2
- 浅谈我国农村土地产权制度与城乡二元经济结构转型
- 2018年春季期一年级数学下册教学计划
- 政治学院学生单项奖奖励办法
- 某集团公司财务管理制度体系制度
- 电力、照明施工方案_secret
- 八年级地理上册期末试卷含答案(人教版)7(可打印修改) (2)
- 2010年个人年度总结及2011年度计划
- 班级管理之幼儿及低龄段的课堂秩序调控
- 钛合金焊接过程应力应变特点分析
- 2011新视野大学英语(第二)第一册_课后翻译原题及答案
- 中国移动12580客户服务分析
- 巧用端口映射 不通过网关开放任意内网