netlink浅谈

  • 什么是netlink

IPC。用户进程之间,用户进程和内核之间。

是双向通信机制。比syscallioctlprocfs优越。

socket,故具有socket API

是异步通信方式

UDP,支持多播,sub/pub机制

内核模块化设计,可动态加载

总结:支持多播双向异步socket方式模块化通信机制。

  • 哪些应用场景?

NETLINK_ROUTEUSERSOCKFIREWALLNETFILTERKOBJECT_UEVENGENERIC

总结:内核路由子系统、防火墙等都在使用,用户进程的工具iproute2等也在使用。

  • 举个简单例子说明使用怎么使用

/* netlink简单示例之: 检测网络设备up/down状态  */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/route.h>
#include <errno.h>

void parse_rtattr(struct rtattr **tb, int max, struct rtattr *attr, int len)  
{  
    for (; RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) {   
        if (attr->rta_type <= max) {   
            tb[attr->rta_type] = attr;  
        }   
    }   
}

int main(int argc, char **argv)
{
    int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
    if (fd < 0) {
        perror("netlink socket fail");
        exit(-1);
    }
       
    struct sockaddr_nl sa;  // { nl_family, nl_pad, nl_pid, nl_groups }
    bzero(&sa, sizeof(sa));     
    sa.nl_family = AF_NETLINK;
    sa.nl_groups = RTMGRP_LINK;

    if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
        perror("bind error");
        close(fd);
        exit(-1);
    }

    int rlen;
    socklen_t len = 0;
    char buff[2048] = {0};
    struct sockaddr_nl addr;
    struct nlmsghdr *nlh;
    struct rtattr *tb[IFLA_MAX + 1];
    struct ifinfomsg *ifinfo;
    int msglen = 0;
    
    /* 仅供演示,实际使用还是select/epoll */
    while(1) {
        len = sizeof(addr);
        rlen = recvfrom(fd, (void *)buff, sizeof(buff), 0, (struct sockaddr *)&addr, &len);
        if (rlen>0) {
            nlh = (struct nlmsghdr *)buff;
            for (; NLMSG_OK(nlh, rlen); nlh = NLMSG_NEXT(nlh, rlen)) {
                switch(nlh->nlmsg_type) {
                    case RTM_NEWLINK:
                    case RTM_DELLINK:
                        bzero(tb, sizeof(tb));
                        ifinfo = NLMSG_DATA(nlh);  
                        msglen = nlh->nlmsg_len - NLMSG_SPACE(sizeof(*ifinfo));  
                        parse_rtattr(tb, IFLA_MAX, IFLA_RTA (ifinfo), msglen);                          
                        if (tb[IFLA_IFNAME]) {   
                            printf("%s:%s\n", RTA_DATA(tb[IFLA_IFNAME]), (ifinfo->ifi_flags & IFF_UP) ? "up" : "down");  
                        }
                        break;
                    default:
                        break;
                }
            }
        } else {
            sleep(1);
        }
    }

    /* 仅供演示,实际使用还是要处理退出 */
    close(fd);
    return 0;        
}
[root@~]#./a.out &
[root@~]#
[root@~]#ifconfig eth0 down up
eth0:down
eth0:up
  • 点评上述例子

“支持多播双向异步socket方式模块化通信机制”,体现了哪些特点:

多播(订阅)---可运行多个实例,每个都会收到消息;

异步;socket方式;

代码量:相比PF_INET + ioctl的方式,为同一级别,比ioctl略少,这里只有设备up/down状态监测,如果是还需要其它功能,例如路由信息变化,那相比ioctl的优势会明显体现。

  • 再来一个例子

来看iproute2。这个项目的librtnetlink提供下面API

  1. rtnl_open_byproto 创建AF_NETLINK socket

  2. rtnl_talk 最终调用sendmsgrecvmsg通过netlink socket和内核通信

  3. rtnl_listen 最终调用recvmsg通过netlink socket收内核发来的消息

最终编译成ip可执行程序的ip.c,定义cmd结构体数组用来处理选项,例如用户的命令:ip route add default dev eth0,就是通过do_iproute来处理的。

static const struct cmd {
	const char *cmd;
	int (*func)(int argc, char **argv);
} cmds[] = {
	{ "address", 	do_ipaddr },
	{ "addrlabel",	do_ipaddrlabel },
	{ "maddress",	do_multiaddr },
	{ "route",	do_iproute },
……
	{ 0 }
};

调用链: main  - - ->  do_cmd  - - ->  do_iproute  - - ->  iproute_modify   - - ->  rtnl_talk   - - ->  sendmsg & recvmsg

  • 深入一层,看看内核怎么实现的

因为呈现给用户的最终还是一个socket,从用户的角度来看,内核要实现一个DGRAM socket,利用L4 socket组件填充socketbindsendmsgrecvmsg系统调用。

双向异步socket方式多播:内部是DGRAM类型socket,因此天然具备这些特性。

内核实现进一步分析另文。

猜你喜欢

转载自blog.csdn.net/quending/article/details/86071639