linux 下的 iptables/ netfilter 防火墙 深度理解 中篇

本文深入探讨Linux系统中iptables用户空间与netfilter内核空间的交互,解析规则如何从用户空间传递到内核并生效,以及内核如何处理这些规则。通过分析iptables源码和内核调用流程,揭示了iptables如何通过libiptc库与内核进行规则交互,并介绍了内核中netfilter如何管理规则和钩子函数的注册与处理。
摘要由CSDN通过智能技术生成

一 概述

前篇主要提到了用户空间 iptables 1.3.5 源码对规则的处理。但是并没有涉及内核空间netfliter 模块的处理。用户空间上的规则要生效最终肯定是通过传给内核空间的netfilter,让netfliter这个老大哥处理。因此抛出两个问题。

  • 用户空间的是怎么获取内核空间已经存在的规则,或者用户空间是如何将需要netfilter处理的规则下发给内核?简而言之,规则在用户空间是如何跟内核空间交互的。
  • netfilter 内核收到用户空间的请求,规则在内核是怎么处理的?最终又是怎么让规则起作用,达到防火墙预想的功能呢?

本篇主要解决这两个问题

二 iptables 用户空间 与 netfilter 内核空间规则的交互

本章深入的内核版本是比较新的4.18.15 版本。
在说明内核,用户间如何交互时,前篇有张图其实很清楚了展示了交互的过程,如下图所示
netfilter 与 iptables 关系
这幅图,很直观的反应了用户空间的iptables和内核空间的基于Netfilter的ip_tables模块之间的关系和其通讯方式,以及Netfilter在这其中所扮演的角色。
从图中可以看出iptables 是通过setsockopt,getsockopt 系统调用获取传递规则链的。
那么iptables 1.3.5 源码是真的这么实现的吗?前篇似乎并没有看到这两个系统调用。
其实不然,iptables 操作规则跟内核交互都交给了libiptc.so库实现了。

1.用户层的交互处理

libiptc.so 库里调用的iptc_init() ,iptc_commit() 里面分配调用了setsockopt,getsockopt
iptc_init() 函数

iptc_handle_t iptc_init(const char *tablename);
#define TC_INIT        iptc_init //宏定义
#define TC_HANDLE_T  iptc_handle_t  //宏定义
TC_HANDLE_T
TC_INIT(const char *tablename)
{
	TC_HANDLE_T h;
	STRUCT_GETINFO info;
	unsigned int tmp;
	socklen_t s;

	iptc_fn = TC_INIT;

	if (strlen(tablename) >= TABLE_MAXNAMELEN) {
		errno = EINVAL;
		return NULL;
	}
	
	if (sockfd_use == 0) {
		sockfd = socket(TC_AF, SOCK_RAW, IPPROTO_RAW);
		if (sockfd < 0)
			return NULL;
	}
	sockfd_use++;

	s = sizeof(info);

	/*----------------------关键部分一 start-----------------------*/
	strcpy(info.name, tablename);
	if (getsockopt(sockfd, TC_IPPROTO, SO_GET_INFO, &info, &s) < 0) {
		if (--sockfd_use == 0) {
			close(sockfd);
			sockfd = -1;
		}
		return NULL;
	}
	/*---------------------------end------------------------------*/

	DEBUGP("valid_hooks=0x%08x, num_entries=%u, size=%u\n",
		info.valid_hooks, info.num_entries, info.size);

	if ((h = alloc_handle(info.name, info.size, info.num_entries))
	    == NULL) {
		if (--sockfd_use == 0) {
			close(sockfd);
			sockfd = -1;
		}
		return NULL;
	}

	
	/* Initialize current state */
	/*----------------------关键部分二start-----------------------*/
	h->info = info;

	h->entries->size = h->info.size;

	tmp = sizeof(STRUCT_GET_ENTRIES) + h->info.size;

	if (getsockopt(sockfd, TC_IPPROTO, SO_GET_ENTRIES, h->entries,
		       &tmp) < 0)
		goto error;
 /*-----------------------end--------------------------------*/
#ifdef IPTC_DEBUG2
	{
		int fd = open("/tmp/libiptc-so_get_entries.blob", 
				O_CREAT|O_WRONLY);
		if (fd >= 0) {
			write(fd, h->entries, tmp);
			close(fd);
		}
	}
#endif

	if (parse_table(h) < 0)
		goto error;

	CHECK(h);
	return h;
error:
	if (--sockfd_use == 0) {
		close(sockfd);
		sockfd = -1;
	}
	TC_FREE(&h);
	return NULL;
}

接下来分析代码中标志的关键部分
关键部分一:
strcut STRUCT_GETINFO info;
getsockopt(sockfd, TC_IPPROTO, SO_GET_INFO, &info, &s);
根据info.name 告知内核需要那张表的规则信息。然后内核会返回这张表内规则的想关信息。例如这张表管理了几条链,这张表有多少条规则,规则的占用的空间大小等等
关键部分二:
getsockopt(sockfd, TC_IPPROTO, SO_GET_ENTRIES, h->entries, &tmp) < 0
再根据SO_GET_INFO获取的表内规则的信息,内核会再把这表上的具体规则数据返回给用户。

iptc_commit() 函数 (本函数比较长,只列出了关键代码)

int iptc_commit(iptc_handle_t *handle);
#define TC_COMMIT   iptc_commit//宏定义
int
TC_COMMIT(TC_HANDLE_T *handle)
{
       						.
       						.
       						.
	ret = setsockopt(sockfd, TC_IPPROTO, SO_SET_REPLACE, repl,
			 sizeof(*repl) + repl->size);
	if (ret < 0) {
		errno = ret;
		goto out_free_newcounters;
	}
							.
							.
							.
	ret = setsockopt(sockfd, TC_IPPROTO, SO_SET_ADD_COUNTERS,
			 newcounters, counterlen);
	if (ret < 0) {
		errno = ret;
		goto out_free_newcounters;
	}
							.
							.
							.

本函数大部分都在填充要传递到内核的STRUCT_REPLACE *repl;STRUCT_COUNTERS_INFO *newcounters;两个结构体,然后下发到内核。
STRUCT_REPLACE *repl 后面内核的时候会做详细讲解,保存着链上的规则。
而newcounters 结构体是干嘛用的呢?每条规则entry都一个计数器,用来记录该规则处理了多少数据包。
至此用户层如何跟内核交互已经了解了大概。

  • setsockopt新增命令字:
  1. #define SO_SET_REPLACE//设置规则

  2. #define SO_SET_ADD_COUNTERS //加入计数器

  • getsockopt新增命令字;
  1. #define SO_GET_INFO //获取ipt_info

  2. #define SO_GET_ENTRIES //获取规则

但是内核层又是怎么处理用户层的请求的呢?

2 内核层的交互处理


上图是之前大神总结的用户层调用setsockopt 系统调用时内核层是如何一步一步处理的。本人也跟着上图步骤验证过。

// socket.c 文件中
static int __sys_setsockopt(int fd, int level, int optname,
			    char __user *optval, int optlen)
{
	int err, fput_needed;
	struct socket *sock;

	if (optlen < 0)
		return -EINVAL;

	sock = sockfd_lookup_light(fd, &err, &fput_needed);
	if (sock != NULL) {
		err = security_socket_setsockopt(sock, level, optname);
		if (err)
			goto out_put;

		if (level == SOL_SOCKET)
			err =
			    sock_set
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值