這篇筆記記錄了AF_INET協定族的filter表的初始化,通過filter表,可以看出協定族是如何與Netfilter架構配合,完成防火牆規則的過濾和管理的。filter表涉及内容較多,分兩篇筆記完成,涉及的核心代碼檔案有:
代碼路徑 | 說明 |
---|---|
net/ipv4/netfilter/iptable_filter.c | IPv4 filter表實作 |
filter表以子產品的形式存在,其控制開關為:CONFIG_IP_NF_FILTER。
1. 初始化
初始化函數即子產品初始化函數,如下:
static int __net_init iptable_filter_net_init(struct net *net)
{
//注冊filter表
net->ipv4.iptable_filter =
ipt_register_table(net, &packet_filter, &initial_table.repl);
if (IS_ERR(net->ipv4.iptable_filter))
return PTR_ERR(net->ipv4.iptable_filter);
return 0;
}
static struct pernet_operations iptable_filter_net_ops = {
.init = iptable_filter_net_init,
.exit = iptable_filter_net_exit,
};
static int __init iptable_filter_init(void)
{
int ret;
if (forward < 0 || forward > NF_MAX_VERDICT) {
printk("iptables forward must be 0 or 1\n");
return -EINVAL;
}
/* Entry 1 is the FORWARD hook */
initial_table.entries[1].target.verdict = -forward - 1;
ret = register_pernet_subsys(&iptable_filter_net_ops);
if (ret < 0)
return ret;
//向Netfilter架構注冊三個鈎子:INPUT、OUTPUT、FORWARD
ret = nf_register_hooks(ipt_ops, ARRAY_SIZE(ipt_ops));
if (ret < 0)
goto cleanup_table;
return ret;
...
}
初始化幹了兩件重要的事情:
- 注冊filter表
- 注冊鈎子
内容較多,這篇筆記隻看filter表的注冊過程,鈎子的注冊過程見下一篇筆記。
2. filter表的注冊
在看具體的實作之前,需要先來看看系統資料庫時傳入的兩個參數:packet_filter和initial_table.repl。
//filter表中的規則隻能在LOCAL_IN、FORWARD、LOCAL_OUT三個點工作
#define FILTER_VALID_HOOKS ((1 << NF_INET_LOCAL_IN) | \
(1 << NF_INET_FORWARD) | \
(1 << NF_INET_LOCAL_OUT))
//packet_filter就是Netfilter架構定義的表結構struct xt_table對象,這裡表名字為filter
static struct xt_table packet_filter = {
.name = "filter",
.valid_hooks = FILTER_VALID_HOOKS,
.lock = RW_LOCK_UNLOCKED,
.me = THIS_MODULE,
.af = AF_INET,
};
該結構正是Netfilter對table的定義,那麼額外的initial_table.repl又是幹什麼的呢?
static struct
{
struct ipt_replace repl;
//這個組織是很有講究的,緊挨着在repl的後面放了4條規則,因為repl的最後一個成員
//entries是零長度數組,是以其直接指向了這裡的規則。filter表注冊時内置了4調規則
struct ipt_standard entries[3];
struct ipt_error term;
} initial_table;
和注冊相關的核心資料結構是struct ipt_replace。iptables工具在更新規則時實際上是整體替換的,即先從核心dump所有規則,然後修改,最後整體替換,并非單獨修改某一條規則。注冊也可以了解為是一種替換,隻是要替換表原本不存在而已,是以隻要設計上面在某些地方做些許相容,注冊和替換的代碼邏輯是可以複用的。而struct ipt_replace結構就是AF_INET協定族專門為了實作table替換而定義的輔助結構。
/* The argument to IPT_SO_SET_REPLACE. */
struct ipt_replace
{
char name[IPT_TABLE_MAXNAMELEN];
/* Which hook entry points are valid: bitmask. You can't change this. */
unsigned int valid_hooks;
/* Number of entries */
unsigned int num_entries;
/* Total size of new entries */
unsigned int size;
/* Hook entry points. */
unsigned int hook_entry[NF_INET_NUMHOOKS];
/* Underflow points. */
unsigned int underflow[NF_INET_NUMHOOKS];
/* Information about old entries: */
/* Number of counters (must be equal to current number of entries). */
unsigned int num_counters;
/* The old entries' counters. */
struct xt_counters __user *counters;
//末尾指針很重要,指向規則,對于initial_table來說就是repl後面的4條規則
struct ipt_entry entries[0];
};
如Netfilter之table、rule、match、target資料結構中關于struct xt_table和struct xt_table_info的介紹,可以發現struct ipt_replace差不多是這兩個結構的綜合,而且它們共同的字段的含義确實是一緻的。
回頭再來看filter表對struct ipt_replace的初始化:
.repl = {
.name = "filter",
.valid_hooks = FILTER_VALID_HOOKS,
//初始化時有4條規則,如上,initial_table中在repl後面定義了4條規則
.num_entries = 4,
//size大小就是所有規則大小的累加
.size = sizeof(struct ipt_standard) * 3 + sizeof(struct ipt_error),
//因為hook_entry[]和underflow[]分别記錄的是該表在每個hook點上的第一條和
//最後一條規則距離表起點的偏移量,并且目前表中每個hook點隻有一條規則,是以
//它們的初始化值都是相同的
.hook_entry = {
[NF_INET_LOCAL_IN] = 0,
[NF_INET_FORWARD] = sizeof(struct ipt_standard),
[NF_INET_LOCAL_OUT] = sizeof(struct ipt_standard) * 2,
},
.underflow = {
[NF_INET_LOCAL_IN] = 0,
[NF_INET_FORWARD] = sizeof(struct ipt_standard),
[NF_INET_LOCAL_OUT] = sizeof(struct ipt_standard) * 2,
},
//下面是對預設的4條規則的初始化。雖然entries和term并不屬于repl,但是因為它們
//在記憶體上面是連續的,是以可以使用這種方式進行初始化,核心中有很多這樣的小技巧
//三個hook點各放一條無條件接受的規則。這三條規則實際上就是hook點的政策規則,
//它們會永遠位于給hook點上所有規則的末尾,當其它規則都不比對時,交由該規則
//覺得資料包最後的命運
.entries = {
IPT_STANDARD_INIT(NF_ACCEPT), /* LOCAL_IN */
IPT_STANDARD_INIT(NF_ACCEPT), /* FORWARD */
IPT_STANDARD_INIT(NF_ACCEPT), /* LOCAL_OUT */
},
//這裡放置了一條錯誤規則。個人了解:這條規則應該永遠都不會被周遊到,因為在周遊
//規則時是根據hook點周遊的,而hook點規則由hook_entry[]和underflow[]定界,永遠
//都不應該走到這裡,放着這麼一條應該是為了對代碼異常場景提供一種保護
.term = IPT_ERROR_INIT, /* ERROR */
},
2.1 ipt_register_table()
表的注冊并不是直接調用Netfilter架構提供的xt_register_table(),AF_INET協定族對注冊過程有特殊處理,是以對注冊過程用ipt_register_table()進行了封裝。
struct xt_table *ipt_register_table(struct net *net, struct xt_table *table,
const struct ipt_replace *repl)
{
int ret;
struct xt_table_info *newinfo;
//該結構就是為了複用表替換代碼而虛構出來的表
struct xt_table_info bootstrap
= { 0, 0, 0, { 0 }, { 0 }, { } };
void *loc_cpu_entry;
struct xt_table *new_table;
//按照規則大小配置設定struct xt_table_info結構
newinfo = xt_alloc_table_info(repl->size);
if (!newinfo) {
ret = -ENOMEM;
goto out;
}
//将repl末尾儲存的規則拷貝到struct xt_table_info中本地CPU的指針位置
loc_cpu_entry = newinfo->entries[raw_smp_processor_id()];
memcpy(loc_cpu_entry, repl->entries, repl->size);
//用指定的資訊檢查新table中的規則資訊(newinfo指向)
ret = translate_table(table->name, table->valid_hooks,
newinfo, loc_cpu_entry, repl->size,
repl->num_entries, repl->hook_entry, repl->underflow);
if (ret != 0)
goto out_free;
//調用Netfilter架構的tablle注冊接口将filter表注冊到系統中
new_table = xt_register_table(net, table, &bootstrap, newinfo);
if (IS_ERR(new_table)) {
ret = PTR_ERR(new_table);
goto out_free;
}
return new_table;
...
}
2.1.1 translate_table()
如注釋所述,該函數的作用就是用調用者指定的資訊校驗并初始化newinfo,其實就是對要注冊的表内容進行合法性檢查。
/* Checks and translates the user-supplied table segment (held in newinfo) */
static int translate_table(const char *name, unsigned int valid_hooks,
struct xt_table_info *newinfo, void *entry0, unsigned int size,
unsigned int number, const unsigned int *hook_entries,
const unsigned int *underflows)
{
unsigned int i;
int ret;
//規則總的占用位元組數和規則數目
newinfo->size = size;
newinfo->number = number;
/* Init all hooks to impossible value. */
for (i = 0; i < NF_INET_NUMHOOKS; i++) {
newinfo->hook_entry[i] = 0xFFFFFFFF;
newinfo->underflow[i] = 0xFFFFFFFF;
}
duprintf("translate_table: size %u\n", newinfo->size);
i = 0;
//檢查規則内的偏移量成員指定的是否準确
ret = IPT_ENTRY_ITERATE(entry0, newinfo->size, check_entry_size_and_hooks,
newinfo, entry0, entry0 + size, hook_entries, underflows, &i);
if (ret != 0)
return ret;
//i記錄的是檢測通過的規則數目,如果和指定的數字不一緻,說明有規則沒有通過檢查
if (i != number) {
duprintf("translate_table: %u not %u entries\n", i, number);
return -EINVAL;
}
//在上一步的檢測過程中如果規則檢測pass,會對hook_entry和underflow指派,
//這裡檢查是否所有有效的HOOK點都已經指定了合理的值
for (i = 0; i < NF_INET_NUMHOOKS; i++) {
/* Only hooks which are valid */
if (!(valid_hooks & (1 << i)))
continue;
if (newinfo->hook_entry[i] == 0xFFFFFFFF) {
duprintf("Invalid hook entry %u %u\n", i, hook_entries[i]);
return -EINVAL;
}
if (newinfo->underflow[i] == 0xFFFFFFFF) {
duprintf("Invalid underflow %u %u\n", i, underflows[i]);
return -EINVAL;
}
}
//檢查表中的規則是否構成環路
if (!mark_source_chains(newinfo, valid_hooks, entry0))
return -ELOOP;
//無論是match還是target,都有對應的check回調,這裡檢查每條規則的match和target
i = 0;
ret = IPT_ENTRY_ITERATE(entry0, newinfo->size, find_check_entry, name, size, &i);
if (ret != 0) {
//沒有通過檢查,清除所有的規則
IPT_ENTRY_ITERATE(entry0, newinfo->size, cleanup_entry, &i);
return ret;
}
//通過了所有的檢查,将規則資訊拷貝到其它CPU的記憶體中
for_each_possible_cpu(i) {
if (newinfo->entries[i] && newinfo->entries[i] != entry0)
memcpy(newinfo->entries[i], entry0, newinfo->size);
}
return ret;
}
如上,注冊過程中,會逐個檢查規則資訊,而周遊過程都是用宏IPT_ENTRY_ITERATE實作的,該宏的定義比較複雜,難以了解,這裡直接看展開後的結果:
{
unsigned int __i, __n;
int __ret = 0;
struct ipt_entry *__entry;
//size是表中所有規則的大小,__entry->next_offset儲存的是距下一條規則的偏移
for (__i = 0, __n = 0; __i < (size); __i += __entry->next_offset, __n++) {
//指向一條規則的起點
__entry = (void *)(entries) + __i;
//__n在本場景不可能會小于0
if (__n < 0)
continue;
//如果回調函數fn()傳回非0,結束周遊,并且傳回非0值
__ret = fn(__entry , ## args);
if (__ret != 0)
break;
}
__ret;
}
2.1.1.1 規則大小檢查:check_entry_size_and_hooks()
static int check_entry_size_and_hooks(struct ipt_entry *e,
struct xt_table_info *newinfo, unsigned char *base, unsigned char *limit,
const unsigned int *hook_entries, const unsigned int *underflows,
unsigned int *i)
{
unsigned int h;
//邊界對齊和規則長度是否超過了總的大小
if ((unsigned long)e % __alignof__(struct ipt_entry) != 0
|| (unsigned char *)e + sizeof(struct ipt_entry) >= limit) {
duprintf("Bad offset %p\n", e);
return -EINVAL;
}
//距下一條規則的偏移不合理,因為一條規則至少包含struct ipt_entry和struct
//ipt_entry_target,是以next_offset一定大于這兩個結構體的長度
if (e->next_offset < sizeof(struct ipt_entry) + sizeof(struct ipt_entry_target)) {
duprintf("checking: element %p size %u\n", e, e->next_offset);
return -EINVAL;
}
//如果指定規則屬于某個HOOK點的邊界規則,指派hook_entry和underflow
for (h = 0; h < NF_INET_NUMHOOKS; h++) {
if ((unsigned char *)e - base == hook_entries[h])
newinfo->hook_entry[h] = hook_entries[h];
if ((unsigned char *)e - base == underflows[h])
newinfo->underflow[h] = underflows[h];
}
//規則命中的計數器清零
e->counters = ((struct xt_counters) { 0, 0 });
e->comefrom = 0;
//規則檢查通過,計數器累加1
(*i)++;
return 0;
}
2.1.1.2 校驗規則:find_check_entry()
每條規則都是有0個或多個match以及1個target組成,每個match和target都有自己特有的參數,在校驗規則時需要回調match和target的check_xxx()回調對規則進行校驗。
static int find_check_entry(struct ipt_entry *e, const char *name,
unsigned int size, unsigned int *i)
{
struct ipt_entry_target *t;
struct xt_target *target;
int ret;
unsigned int j;
//對規則中的标準比對以及target之間的偏移量進行校驗
ret = check_entry(e, name);
if (ret)
return ret;
//對規則中match進行校驗,包括該match是否注冊以及調用match自身check()回調
j = 0;
ret = IPT_MATCH_ITERATE(e, find_check_match, name, &e->ip, e->comefrom, &j);
if (ret != 0)
goto cleanup_matches;
//擷取規則中的target,檢查該target是否存在
t = ipt_get_target(e);
target = try_then_request_module(xt_find_target(AF_INET, t->u.user.name,
t->u.user.revision), "ipt_%s", t->u.user.name);
if (IS_ERR(target) || !target) {
duprintf("find_check_entry: `%s' not found\n", t->u.user.name);
ret = target ? PTR_ERR(target) : -ENOENT;
goto cleanup_matches;
}
//找到對應的target子產品,用u.kernel.target指向該target
t->u.kernel.target = target;
//調用target的check()回調校驗target
ret = check_target(e, name);
if (ret)
goto err;
(*i)++;
return 0;
...
}