一 概述
前篇主要提到了用户空间 iptables 1.3.5 源码对规则的处理。但是并没有涉及内核空间netfliter 模块的处理。用户空间上的规则要生效最终肯定是通过传给内核空间的netfilter,让netfliter这个老大哥处理。因此抛出两个问题。
- 用户空间的是怎么获取内核空间已经存在的规则,或者用户空间是如何将需要netfilter处理的规则下发给内核?简而言之,规则在用户空间是如何跟内核空间交互的。
- netfilter 内核收到用户空间的请求,规则在内核是怎么处理的?最终又是怎么让规则起作用,达到防火墙预想的功能呢?
本篇主要解决这两个问题
二 iptables 用户空间 与 netfilter 内核空间规则的交互
本章深入的内核版本是比较新的4.18.15 版本。
在说明内核,用户间如何交互时,前篇有张图其实很清楚了展示了交互的过程,如下图所示
这幅图,很直观的反应了用户空间的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新增命令字:
-
#define SO_SET_REPLACE//设置规则
-
#define SO_SET_ADD_COUNTERS //加入计数器
- getsockopt新增命令字;
-
#define SO_GET_INFO //获取ipt_info
-
#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