一个以介绍并总结Linux/Unix类操作系统技术、软件开发技术、数据库技术和网络应用技术等为主的开源技术
分类: 网络与安全
2023-06-03 21:58:23
{BANNED}最佳近在做主机侧网络连接相关的调研,发现通过 linux 自带的netlink能实时获取网络连接五元组信息,可以用于网络活动可视化、异常连接检测、安全策略优化以及审计等功能,但网络上找到的相关文章不多,因此在此分析下netlink实时获取网络连接信息的原理。
netlink是什么?官方文档是这样描述的:
Netlink is used to transfer information between the kernel and user-space processes. It consists of a standard sockets-based interface for user space processes and an internal kernel API for kernel modules.
Netlink用于在内核和用户空间进程之间传输信息。它由一个面向用户空间进程的标准套接字接口和一个面向内核模块的内部内核API组成。
简单来讲netlink实现了一套用户态和内核态通信的机制,通过netlink能和很多内核模块通信,例如ss命令通过 netlink 更快的获取网络信息、hids通过netlink获取进程创建信息、iptables通过netlink配置网络规则等。
关于如何使用netlink官方提供了如下使用样例:
点击(此处)折叠或打开
点击(此处)折叠或打开
这里列举了几个安全常用的协议,通过NETLINK_INET_DIAG可以获取网络连接数据,通过NETLINK_AUDIT可以获取内核auditd数据,通过NETLINK_CONNECTOR可以获取进程创建数据。而NETLINK_NETFILTER正是本文后续要介绍的能实时获取网络连接信息的协议。
为什么socket()指定AF_NETLINK之后就能和netlink通信了? 需要结合netlink的初始化过程来看。
点击(此处)折叠或打开
点击(此处)折叠或打开
点击(此处)折叠或打开
net_families是net_proto_family类型的数据,此时net_families[PF_NETLINK]->create == netlink_create,到此也就完成了模块初始化过程。
系统调用socket时会调用net_families中的netlink_create:
点击(此处)折叠或打开
可以看到系统调用socket{BANNED}最佳终会调用net_families[family]->create,而官方样例中我们是这样fd = socket(AF_NETLINK, SOCK_RAW, netlink_family)创建 netlink socket的,include/linux/socket.h文件中定义#define PF_NETLINK AF_NETLINK,因此net_families[family]->create调用的正是netlink_create!我们再来看看netlink_create做了什么:
可以看到netlink_create进行了sock->ops = &netlink_ops赋值操作,也就是说我们可以通过socket(AF_NETLINK, SOCK_RAW, netlink_family)获得一个socket,此时该socket对应的bind sendmsg recvmsg等函数都被替换成了netlink协议的对应函数。同时创建了socket->sk与netlink_sock结构体关联,netlink_sock中会存放netlink通信过程中需要的dst_pid、dst_group等数据。
各个内核模块注册netlink协议的流程基本相同,本节以后续要分析的NETLINK_NETFILTER协议为例分析一下注册netlink协议的流程:
可以看到netfilter并不是默认开启的内核模块,当该模块被加载时会调用netlink_kernel_create
netlink_kernel_create返回给netfilter内核模块一个socket,并切设置了该socket的netlink_rcv函数为nfnetlink_rcv,{BANNED}最佳后还更新了nl_table设置NETLINK_NETFILTER协议为激活状态。
用户态可以通过send向内核态发送netlink消息,通过分析这个过程我们可以看到nfnetlink_rcv和nl_table的作用:
send{BANNED}最佳终调用的是sock->ops->sendmsg也就是netlink_sendmsg
而netlink_sendmsg会根据dst_group选择执行netlink_broadcast和netlink_unicast,netlink正是用这两个函数将数据单播、广播给发送给指定的进程或内核模块。用户态发送给内核进程的消息自然用的是单播netlink_unicast:
可以看到netlink_unicast单播时会通过netlink_is_kernel判断消息是否要发往内核,如果消息需要发送到内核则会调用内核模块初始化时注册的input函数nfnetlink_rcv,至此完成从用户态发送消息到指定内核模块的流程。
内核发送数据到用户态同样需要借助于netlink_unicast netlink_broadcast函数:
netlink_unicast和netlink_broadcast{BANNED}最佳终都会调用skb_queue_tail将数据写入sk->sk_receive_queue队列中。
用户态则可以使用recvmsg接收来自内核的消息:
recvmsg{BANNED}最佳终调用的还是sock->ops->recvmsg即netlink_rcvmsg:
接收netlink消息的流程非常简单,就是从socket->sk->sk_receive_queue中拷贝消息到指定的msghdr->msg_iov中。
netfilter 是一个内核框架,用于在 Linux 系统中进行网络数据包过滤、修改和网络地址转换等操作。为了实现这些功能netfilter在一些位置埋了钩子,例如在 IP 层的入口函数ip_rcv中,netfilter通过NF_HOOK处理数据包:
可以看到NF_HOOK{BANNED}最佳终会进入nf_iterate遍历执行nf_hooks[pf][hook]中的hook,我们来看一下nf_hooks是怎么被初始化的:
nf_conntrack_l3proto_ipv4内核模块实现了IPV4的连接追踪功能,可以看到其将ipv4_conntrack_ops注册到nf_hooks中,再来看一下ipv4_conntrack_ops做了什么:
NF_HOOK{BANNED}最佳终会通过nf_hooks执行nf_conntrack_event_cb->fcn,nf_conntrack_event_cb 是一个全局指针,其中存储了网络连接跟踪事件的回调函数
nf_conntrack_event_cb 在net/netfilter/nf_conntrack_netlink.c中完成了注册指向了ctnl_notifier:
nf_conntrack_event_cb->fcn会执行到ctnl_notifier.fcn也就是ctnetlink_conntrack_event,ctnetlink_conntrack_event会通过ctnetlink_dump_xxx系列函数将item->ct的数据拷贝至skb中,{BANNED}最佳后通过nfnetlink_send发送数据到指定的组。
显然如果我们加入NETLINK_NETFILTER的NFNLGRP_CONNTRACK_NEW组就能实时获取新网路连接信息了。可以使用setsockopt加入指定的组:setsockopt(fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group))
用户态通过setsockopt即可调用netlink_setsockopt将socket加入指定的组用于接收来自指定内核模块的数据。
netfilter conntrack本身是一种成熟的网络连接状态信息跟踪方案,其功能正符合“新建网络连接监控”这一场景,使其在性能、实时性、兼容性方面都有不错的表现。可以通过conntrack-tools快速体验一下这个监控方案:
但在测试过程中发现系统内核版本相同的机器,有的可以使用netlink获取实时网络连接信息,有的不行。分析源码不难发现netlink模块是通过core_initcall(netlink_proto_init)默认加载的,而netfilter的相关模块是不会默认加载的。通过Makefile也可以看到nf_conntrack_ipv4内核模块中包含了我们需要使用的nf_conntrack_l3proto_ipv4:
因此如果要通过netlink获取实时网络连接信息需要加载这几个内核模块:nfnetlink nf_conntrack nf_defrag_ipv4 nf_conntrack_netlink nf_conntrack_ipv4,使用modprobe命令加载nf_conntrack_netlink nf_conntrack_ipv4即可自动加载其他依赖模块。