netlink 通信机制

下面的代码部分取自于:http://blog.chinaunix.net/uid-14753126-id-2983915.html

不过在该基础上修改了一下。(部分我认为是不合理的)

希望有所帮助咯~~~~~

一、自定义协议
netlik编程,有几个地方需要注意的。
1.就是数据包的格式.
struct nlmsghdr + padding + payload + padding
1,1 数据包占用的内存大小空间(包括paddnig)由宏NLMSG_SPACE可以获取到,它就是用来调用sendto函数时候传递过去的长度参数,
指明了数据包占用的内存大小空间。
1.2 除了sendto函数参数需要指明数据占据空间大小之后,struct nlmsghdr的字段__u32 nlmsg_len; /* Length of message including header */
我认为,该字段是用来保存对齐后的头,加上payload长度(没有对齐)。通过宏NLMSG_LENGTH的返回值作为该字段的值。之所以这样认为,因为在内核
才可以得到payload的有效长度,通过struct nlmsghdr->nlmsg_len - NLMSG_ALIGN(NLMSG_HDRLEN)就可以得到payload的有效长度。
这个猜想,是可以通过内核函数__nlmsg_put来验证的,该函数如下:
static __inline__ struct nlmsghdr * __nlmsg_put(struct sk_buff *skb, u32 pid, u32 seq, int type, int len, int flags)
{
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 = flags;
nlh->nlmsg_pid = pid;
nlh->nlmsg_seq = seq;
if (!__builtin_constant_p(size) || NLMSG_ALIGN(size) - size != 0)
memset(NLMSG_DATA(nlh) + len, 0, NLMSG_ALIGN(size) - size);
return nlh;
}
显然,它设置的nlmsg_len就是有效大小!!!
 
实际当中,假设假设对内核的某个状态感兴趣,就可以把该消息发送出来给应用层。就下面的示例来说,可以在稍加修改。
内核一个静态变量保存了应用层的PID号,一开始可以初始化为0,当收到应用层的消息的时候,设置接收消息的PID号,这样
内核就可以发送消息给应用层了。(因为知道了接受消息的PID号)。
对内核来说,只有在PID号不为0的情况下,才需要去发送消息。(不为0的情况下,证明说应用程序已经启动了)
 
netlink用户程序:
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <linux/netlink.h>
 
#define NETLINK_TEST 17
#define MSG_LEN 100
 
struct msg_to_kernel
{
struct nlmsghdr hdr;
char data[MSG_LEN];
};
struct u_packet_info
{
struct nlmsghdr hdr;
char msg[MSG_LEN];
};
 
int main(int argc, char* argv[])
{
char *data = "abcd";
int real_size = NLMSG_SPACE(strlen(data) + 1); //这个宏非常的重要!!需要区别他们之间的不同..(我这里是简单的命名为
int effective_size = NLMSG_LENGTH(strlen(data) + 1); //有效大小和实际大小)有效大小为没有对齐后的大小,也就是真正payload数据长度。
struct sockaddr_nl local; //而且有这样的关系 readl_size >= effective_size. 当有效大小为4的倍数的时候
struct sockaddr_nl kpeer; //就不需要对齐了,此时read_size = effective_size.
int skfd, ret, kpeerlen = sizeof(struct sockaddr_nl);
struct nlmsghdr *message;
struct u_packet_info info;
char *retval;
 
message = (struct nlmsghdr *)malloc(real_size);
//创建socket的时候指定family为NETLINK,并且要指定协议。这里是用户自定义的一个协议。
skfd = socket(PF_NETLINK, SOCK_RAW, NETLINK_TEST);
if(skfd < 0){
printf("can not create a netlink socket\n");
return -1;
}
memset(&local, 0, sizeof(local));
local.nl_family = AF_NETLINK;
local.nl_pid = getpid();
local.nl_groups = 0;
//调用bind命令,把当前的socket绑定一个pid号(类似于端口号),同时绑定可以确定该socket是否监听组的消息。
//相同协议(eg:NETLINK_TEST)的socket,都应该有唯一的pid号.
if(bind(skfd, (struct sockaddr *)&local, sizeof(local)) != 0){
printf("bind() error\n");
return -1;
}
//设置接收该消息的socket....(pid号指定一个socket..对于内核来说,它就是0)。同时也指定该消息要不要发送给哪个组。
memset(&kpeer, 0, sizeof(kpeer));
kpeer.nl_family = AF_NETLINK;
kpeer.nl_pid = 0;
kpeer.nl_groups = 0;
 
memset(message, '\0', real_size);
message->nlmsg_len = effective_size;
message->nlmsg_flags = 0;
message->nlmsg_type = 0;
message->nlmsg_seq = 0;
message->nlmsg_pid = local.nl_pid;
 
retval = memcpy(NLMSG_DATA(message), data, effective_size);
 
printf("message sendto kernel are:%s, len:%d\n", (char *)NLMSG_DATA(message), message->nlmsg_len - NLMSG_ALIGN(NLMSG_HDRLEN));
ret = sendto(skfd, message, real_size, 0,(struct sockaddr *)&kpeer, sizeof(kpeer));
if(!ret){
perror("send pid:");
exit(-1);
}
 
//接受内核态确认信息
ret = recvfrom(skfd, &info, sizeof(struct u_packet_info),0, (struct sockaddr*)&kpeer, &kpeerlen);
if(!ret){
perror("recv form kerner:");
exit(-1);
}
 
printf("message receive from kernel:%s\n",(char *)info.msg);
 
close(skfd);
return 0;
}
 
netlink内核程序:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <net/sock.h>
#include <linux/netlink.h>
 
#define NETLINK_TEST 17
struct {
__u32 pid;
}user_process;
 
static struct sock *netlinkfd = NULL;
 
int send_to_user(char *info)
{
int effective_size = strlen(info) + 1;
struct sk_buff *skb;
struct nlmsghdr *nlh;
 
int retval;
skb = nlmsg_new(effective_size, GFP_KERNEL); //这里面会去分配skb...并且分配存放数据的内存块。
if(!skb)
goto err_out;
 
nlh = NLMSG_NEW(skb, 0, 0, 0, effective_size, 0); //这里面就是初始化struct nlmsg_hdr头.
if(!nlh)
goto err_out;
 
memcpy(NLMSG_DATA(nlh), info, effective_size); //拷贝数据到data区
NETLINK_CB(skb).pid = 0; //设置发送该消息的pid号.
NETLINK_CB(skb).dst_group = 0; //设置接收该消息的组号.
 
retval = netlink_unicast(netlinkfd, skb, user_process.pid, MSG_DONTWAIT);
 
return 0;
 
nlmsg_failure:
err_out:
if(skb)
kfree_skb(skb);
return -1;
}
 
void kernel_receive(struct sk_buff *__skb)
{
struct sk_buff *skb;
struct nlmsghdr *nlh = NULL;
 
char *data = "This is eric's test message from kernel";
 
printk("[kernel space] begin kernel_receive\n");
skb = skb_get(__skb);
 
if(skb->len >= sizeof(struct nlmsghdr)){
nlh = (struct nlmsghdr *)skb->data;
if((nlh->nlmsg_len >= sizeof(struct nlmsghdr)) && (__skb->len >= nlh->nlmsg_len)) {
 
user_process.pid = nlh->nlmsg_pid;
printk("[kernel space] data receive from user are:%s\n", (char *)NLMSG_DATA(nlh));
printk("[kernel space] user_pid:%d\n", user_process.pid);
send_to_user(data);
 
}
}else{
printk("[kernel space] data receive from user are:%s\n",(char *)NLMSG_DATA(nlmsg_hdr(__skb)));
send_to_user(data);
}
 
kfree_skb(skb);
}
 
int __init test_netlink_init(void)
{
netlinkfd = netlink_kernel_create(&init_net, NETLINK_TEST, 0, kernel_receive, NULL, THIS_MODULE);
if(!netlinkfd){
printk("can not create a netlink socket\n");
return -1;
}
printk("test_netlink_init!!\n");
return 0;
}
 
void __exit test_netlink_exit(void)
{
sock_release(netlinkfd->sk_socket);
printk("test_netlink_exit!!\n");
}
 
module_init(test_netlink_init);
module_exit(test_netlink_exit);
 
MODULE_AUTHOR("eric.hu");
MODULE_LICENSE("GPL");
 
二、接受来自内核的组播消息。
netlink用户程序,组播。
下面这个程序是获取内核组播的消息,当内核检测到说有新的路由添加的时候,它就会组播一条消息,对于需要检测该消息的程序就可以收到咯。
内核发送过来的消息形式为:
struct nlmsghdr + padding + struct rtmsg + padding + struct nlattr + padding + struct nlattr + padding....
一共会收到8个消息,可以查看内核函数fib_add_ifaddr验证结果。
 
 
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
 
int main()
{
struct sockaddr_nl local;
struct sockaddr_nl kpeer;
int skfd, rcv_size, kpeerlen = sizeof(struct sockaddr_nl);
char data[4096];
struct nlmsghdr *hdr;
struct rtmsg *rt;
//struct nlattr + padding + payload + padding.
struct nlattr *attr;
unsigned int attr_size = NLA_ALIGN(NLA_HDRLEN + 4);
unsigned int attr_num, i;
unsigned char *p;
 
skfd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if(skfd < 0){
printf("can not create a netlink socket\n");
return -1;
}
 
memset(&local, 0, sizeof(local));
local.nl_family = AF_NETLINK;
local.nl_pid = getpid();
要监听的组的对应bit需要设置为1,因为bit是从0开始编号的,因此需要减1。
local.nl_groups = 1<<(RTNLGRP_IPV4_ROUTE - 1);
if(bind(skfd, (struct sockaddr *)&local, sizeof(local)) != 0){
printf("bind() error\n");
return -1;
}
 
while(1) {
memset(&kpeer, 0, sizeof(kpeer));
rcv_size = recvfrom(skfd, data, 4096,0, (struct sockaddr*)&kpeer, &kpeerlen);
if(rcv_size <= 0) {
printf("Error, function: recvfrom.\n");
exit(-1);
}
 
hdr = (struct nlmsghdr *)data;
rt = (struct rtmsg *)(data + NLMSG_ALIGN(NLMSG_HDRLEN));
attr = (struct nlattr *)(data + NLMSG_ALIGN(NLMSG_HDRLEN) + NLMSG_ALIGN(sizeof(struct rtmsg)));
printf("type: %d\t", hdr->nlmsg_type);
printf("rtm_dst_len:%d\n", rt->rtm_dst_len);
#if 0
p = attr;
for(i = 0; i < 32; i++) {
printf("%02x ", p[i]);
}
printf("\n");
#endif
attr_num = RTM_PAYLOAD(hdr) / attr_size;
for(i = 0; i < attr_num; i++) {
printf("\tnla_len:%d\t", attr->nla_len);
printf("\tnla_type:%d\t", attr->nla_type);
if(attr->nla_type == 1) {
p = (unsigned char *)attr + NLA_HDRLEN;
printf("%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
}
printf("\n");
//next attr.
attr = (struct nlattr *)((unsigned char *)attr + attr_size);
}
 
#if 0
printf("len: %d\n", hdr->nlmsg_len);
printf("type: %d\n", hdr->nlmsg_type);
printf("pid: %d\n", hdr->nlmsg_pid);
 
printf("rtm_family:%d\n", rt->rtm_family);
printf("rtm_dst_len:%d\n", rt->rtm_dst_len);
printf("nla_len:%d\n", attr->nla_len);
printf("nla_type:%d\n", attr->nla_type);
#endif
}
return 0;
}
执行:
 
 
 
 
 
 
 
显示:
 
下面的宏,会作为struct nlmsghdr-> nlmsg_type的值.
enum {
...
...
RTM_NEWROUTE = 24, 添加路由
#define RTM_NEWROUTE RTM_NEWROUTE
RTM_DELROUTE, 删出路由
#define RTM_DELROUTE RTM_DELROUTE
RTM_GETROUTE,
#define RTM_GETROUTE RTM_GETROUTE
...
...
__RTM_MAX,
#define RTM_MAX (((__RTM_MAX + 3) & ~3) - 1)
};
内核函数:
void fib_add_ifaddr(struct in_ifaddr *ifa)
{
struct in_device *in_dev = ifa->ifa_dev;
struct net_device *dev = in_dev->dev;
struct in_ifaddr *prim = ifa;
__be32 mask = ifa->ifa_mask;
__be32 addr = ifa->ifa_local;
__be32 prefix = ifa->ifa_address&mask;
 
if (ifa->ifa_flags&IFA_F_SECONDARY) {
prim = inet_ifa_byprefix(in_dev, prefix, mask);
if (prim == NULL) {
printk(KERN_WARNING "fib_add_ifaddr: bug: prim == NULL\n");
return;
}
}
//打印出来的type = 24, 192.168.1.173
fib_magic(RTM_NEWROUTE, RTN_LOCAL, addr, 32, prim);
 
if (!(dev->flags&IFF_UP))
return;
 
/* Add broadcast address, if it is explicitly assigned. */
//打印出来的type = 25, 192.168.1.255
if (ifa->ifa_broadcast && ifa->ifa_broadcast != htonl(0xFFFFFFFF))
fib_magic(RTM_NEWROUTE, RTN_BROADCAST, ifa->ifa_broadcast, 32, prim);
 
if (!ipv4_is_zeronet(prefix) && !(ifa->ifa_flags&IFA_F_SECONDARY) && (prefix != addr || ifa->ifa_prefixlen < 32)) {
//打印出来的type = 24, 192.168.1.0
fib_magic(RTM_NEWROUTE, dev->flags&IFF_LOOPBACK ? RTN_LOCAL : RTN_UNICAST, prefix, ifa->ifa_prefixlen, prim);
 
/* Add network specific broadcasts, when it takes a sense */
if (ifa->ifa_prefixlen < 31) {
//打印的type = 24, 192.168.1.0
fib_magic(RTM_NEWROUTE, RTN_BROADCAST, prefix, 32, prim);
//我猜测!!!没有验证...这里也是添加广播路由..因为prefix|~mask就是计算主机部分都为1的情况.
//如果前面的ifa->ifa_broadcast为0的话,那么这里就可以根据ip地址来添加广播路由,那如果设置了ifa->ifa_broadcast,并且
//和prefix|~mask相等..那这里相当于没有添加路由,这也是为什么用户空间获取到的type=25消息只有4条.
fib_magic(RTM_NEWROUTE, RTN_BROADCAST, prefix|~mask, 32, prim);
}
}
}
 
 
发布了21 篇原创文章 · 获赞 1 · 访问量 6103

猜你喜欢

转载自blog.csdn.net/darling54454/article/details/53169581
今日推荐