linux netfilter ----iptable_filter

 內核中將filter模塊被組織成了一個獨立的模塊,每一個這樣獨立的模塊中都有個相似的init()初始化函數;首先來看一下filter模塊是如何將本身的鉤子函數註冊到netfilter所管轄的幾個hook點。bootstrap

filter 模塊鉤子點:數組

/* 在LOCAL_IN,FORWARD, LOCAL_OUT鉤子點工做 */
#define FILTER_VALID_HOOKS ((1 << NF_INET_LOCAL_IN) | \
                (1 << NF_INET_FORWARD) | \
                (1 << NF_INET_LOCAL_OUT))

 

static const struct xt_table packet_filter = {
    .name        = "filter",
    .valid_hooks    = FILTER_VALID_HOOKS,
    .me        = THIS_MODULE,
    .af        = NFPROTO_IPV4,
    .priority    = NF_IP_PRI_FILTER,
    .table_init    = iptable_filter_table_init,
};

 

 

ilist數據結構

struct ipt_standard {
    struct ipt_entry entry;
    struct xt_standard_target target;
};
struct ipt_error {
    struct ipt_entry entry;
    struct xt_error_target target;
};

void *ipt_alloc_initial_table(const struct xt_table *info)
{
    unsigned int hook_mask = info->valid_hooks; //LOCAL_IN、FORWARD、LOCAL_OUT 
    unsigned int nhooks = hweight32(hook_mask); //這裏獲得3,上面hookmask對應三個hook點。
    unsigned int bytes = 0, hooknum = 0, i = 0; 
//看到函數的最後,知道返回值是tbl,而這裏的結構體內嵌的三個結構體是tbl的組成,三個結構體的數據結構拓撲圖如圖11.1.3。
    struct {
        struct ipt_replace repl; 
        struct ipt_standard entries[nhooks]; 
        struct ipt_error term; 
    } *tbl = kzalloc(sizeof(*tbl), GFP_KERNEL); 
    if (tbl == NULL) 
        return NULL; 
    strncpy(tbl->repl.name, info->name, sizeof(tbl->repl.name)); 
    tbl->term = (struct ipt_error)IPT_ERROR_INIT; 
    tbl->repl.valid_hooks = hook_mask; 
    tbl->repl.num_entries = nhooks + 1; 
    tbl->repl.size = nhooks * sizeof(struct ipt_standard) + sizeof(struct ipt_error); 
    for (; hook_mask != 0; hook_mask >>= 1, ++hooknum) {
        if (!(hook_mask & 1)) 
            continue; 
        tbl->repl.hook_entry[hooknum] = bytes; 
        tbl->repl.underflow[hooknum]  = bytes; 
        tbl->entries[i++] = (struct ipt_standard) IPT_STANDARD_INIT(NF_ACCEPT); 
        bytes += sizeof(struct ipt_standard); 
    }
    return  tbl; 
}
/*filter模塊初始化時先調用ipt_register_table向Netfilter完成filter過濾表的註冊,而後調用ipt_register_hooks完成本身鉤子函數的註冊
*/
initial_table.repl= { "filter", FILTER_VALID_HOOKS, 4,
       sizeof(struct ipt_standard) * 3 + sizeof(struct ipt_error),

       { [NF_IP_LOCAL_IN] = 0,
         [NF_IP_FORWARD] = sizeof(struct ipt_standard),
         [NF_IP_LOCAL_OUT] = sizeof(struct ipt_standard) * 2
       },
       { [NF_IP_LOCAL_IN] = 0,
         [NF_IP_FORWARD] = sizeof(struct ipt_standard),
         [NF_IP_LOCAL_OUT] = sizeof(struct ipt_standard) * 2
       },
      0, NULL, { }
};
static int __net_init iptable_filter_table_init(struct net *net) { struct ipt_replace *repl; int err; /* filter表已經被初始化了,返回 */ if (net->ipv4.iptable_filter) return 0; /* 分配初始化表,用於下面的表註冊 */ repl = ipt_alloc_initial_table(&packet_filter); if (repl == NULL) return -ENOMEM; /* Entry 1 is the FORWARD hook */ /* 入口1是否爲FORWARD鉤子點時的verdict值設置 */ ((struct ipt_standard *)repl->entries)[1].target.verdict = forward ? -NF_ACCEPT - 1 : -NF_DROP - 1; err = ipt_register_table(net, &packet_filter, repl, filter_ops, &net->ipv4.iptable_filter); kfree(repl); return err; }

---最後一個柔性數組struct ipt_entry  entries[0]中保存了默認的那四條規則app

 

testsocket

/*
簡而言之ipt_register_table()所作的事情就是從模板initial_table變量的repl成員裏取出初始化數據,而後申請一塊內存並用repl裏的值來初始化它,
以後將這塊內存的首地址賦給packet_filter表的private成員,最後將packet_filter掛載到xt[2].tables的雙向鏈表中。
*/
////iptable netfilter表註冊添加到該鏈表中   iptable_filter.ko裏面用結構xt_table,該表現源從packet_filter來的  見xt_register_table
//table頭部:net->xt.tables[table->af],全部table的頭部鏈表
int ipt_register_table(struct net *net, const struct xt_table *table,
               const struct ipt_replace *repl,
               const struct nf_hook_ops *ops, struct xt_table **res)
{
    int ret;
    struct xt_table_info *newinfo;
    struct xt_table_info bootstrap = {0};
    void *loc_cpu_entry;
    struct xt_table *new_table;

    newinfo = xt_alloc_table_info(repl->size);//malloc  for  xt_table  filter size爲sizeof(struct ipt_standard) * 3 + sizeof(struct ipt_error),
    if (!newinfo)
        return -ENOMEM;

    loc_cpu_entry = newinfo->entries;//將表中的規則入口地址賦值給loc_cpu_entry
    memcpy(loc_cpu_entry, repl->entries, repl->size);//拷貝repl裏面的entries規則到xt_table_info表裏面的entries裏面
    
 /*translate_table函數將由newinfo所表示的table的各個規則進行邊界檢查,
 而後對於newinfo所指的xt_talbe_info結構中的hook_entries和underflows賦予正確的值,
 最後將表項向其餘cpu拷貝*/
    ret = translate_table(net, newinfo, loc_cpu_entry, repl);
    if (ret != 0)
        goto out_free;
/*
packet_filter中沒對其private成員進行初始化,那麼這個工做天然而然的就留給了xt_register_table()函數來完成,它也定義在x_tables.c文件中,它主要完成兩件事:

    1)、將由newinfo參數所存儲的表裏面關於規則的基本信息結構體xt_table_info{}變量賦給由table參數所表示的packet_filter{}的private成員變量;

2)、根據packet_filter的協議號af,將filter表掛到變量xt中tables成員變量所表示的雙向鏈表裏。
*/
    new_table = xt_register_table(net, table, &bootstrap, newinfo);
    if (IS_ERR(new_table)) {
        ret = PTR_ERR(new_table);
        goto out_free;
    }

    /* set res now, will see skbs right after nf_register_net_hooks */
    WRITE_ONCE(*res, new_table);

    ret = nf_register_net_hooks(net, ops, hweight32(table->valid_hooks));
    if (ret != 0) {
        __ipt_unregister_table(net, new_table);
        *res = NULL;
    }

    return ret;

out_free:
    xt_free_table_info(newinfo);
    return ret;
}

 

 

 

Filter回調函數

 在上述ipt_register_table 實現中會調用nf_register_net_hooks  註冊鉤子回調函數tcp

Netfilter中默認表filter在創建時則在NF_IP_LOCAL_IN,NF_IP_FORWARD,NF_IP_LOCAL_OUT鉤子點註冊了鉤子函數iptable_filter_hook,其核心ipt_do_table()對相對應的表和鉤子點的規則進行遍歷函數

static unsigned int
iptable_filter_hook(void *priv, struct sk_buff *skb,
            const struct nf_hook_state *state)
{/* LOCAL_OUT && (數據長度不足ip頭 || 實際ip頭部長度不足最小ip頭),在使用raw socket */
    if (state->hook == NF_INET_LOCAL_OUT &&
        (skb->len < sizeof(struct iphdr) ||
         ip_hdrlen(skb) < sizeof(struct iphdr)))
        /* root is playing with raw sockets. */
        return NF_ACCEPT;
/* 核心規則匹配流程 */
    return ipt_do_table(skb, state, state->net->ipv4.iptable_filter);
}

 

可知其回調函數核心函數爲:fetch

/* Returns one of the generic firewall policies, like NF_ACCEPT. 
包過濾子功能:包過濾一共定義了四個hook函數,這四個hook函數本質最後都調用了ipt_do_table()函數。
其實是直接調用ipt_do_table(ip_tables.c)函數

接下來就是根據table裏面的entry來處理數據包了

一個table就是一組防火牆規則的集合

而一個entry就是一條規則,每一個entry由一系列的matches和一個target組成

一旦數據包匹配了該某個entry的全部matches,就用target來處理它
Match又分爲兩部份,一部份爲一些基本的元素,如來源/目的地址,進/出網口,協議等,對應了struct ipt_ip,
咱們經常將其稱爲標準的match,另外一部份match則以插件的形式存在,是動態可選擇,也容許第三方開發的,
經常稱爲擴展的match,如字符串匹配,p2p匹配等。一樣,規則的target也是可擴展的。這樣,一條規則佔用的空間,
能夠分爲:struct ipt_ip+n*match+n*target,(n表示了其個數,這裏的match指的是可擴展的match部份)。
*/
unsigned int
ipt_do_table(struct sk_buff *skb,
         const struct nf_hook_state *state,
         struct xt_table *table)
{
    unsigned int hook = state->hook;
    static const char nulldevname[IFNAMSIZ] __attribute__((aligned(sizeof(long))));
    const struct iphdr *ip;
    /* Initializing verdict to NF_DROP keeps gcc happy. */
    unsigned int verdict = NF_DROP;
    const char *indev, *outdev;
    const void *table_base;
    struct ipt_entry *e, **jumpstack;
    unsigned int stackidx, cpu;
    const struct xt_table_info *private;
    struct xt_action_param acpar;
    unsigned int addend;

    /* Initialization */
    stackidx = 0;
    ip = ip_hdr(skb);
    indev = state->in ? state->in->name : nulldevname;
    outdev = state->out ? state->out->name : nulldevname;
    /* We handle fragments by dealing with the first fragment as
     * if it was a normal packet.  All other fragments are treated
     * normally, except that they will NEVER match rules that ask
     * things we don't know, ie. tcp syn flag or ports).  If the
     * rule is also a fragment-specific rule, non-fragments won't
     * match it. */
    acpar.fragoff = ntohs(ip->frag_off) & IP_OFFSET;
    acpar.thoff   = ip_hdrlen(skb);
    acpar.hotdrop = false;
    acpar.net     = state->net;
    acpar.in      = state->in;
    acpar.out     = state->out;
    acpar.family  = NFPROTO_IPV4;
    acpar.hooknum = hook;

    IP_NF_ASSERT(table->valid_hooks & (1 << hook));
    local_bh_disable();
    addend = xt_write_recseq_begin();
    private = table->private;
    cpu        = smp_processor_id();
    /*
     * Ensure we load private-> members after we've fetched the base
     * pointer.
     */
    smp_read_barrier_depends();
    table_base = private->entries;
    jumpstack  = (struct ipt_entry **)private->jumpstack[cpu];

    /* Switch to alternate jumpstack if we're being invoked via TEE.
     * TEE issues XT_CONTINUE verdict on original skb so we must not
     * clobber the jumpstack.
     *
     * For recursion via REJECT or SYNPROXY the stack will be clobbered
     * but it is no problem since absolute verdict is issued by these.
     */
    if (static_key_false(&xt_tee_enabled))
        jumpstack += private->stacksize * __this_cpu_read(nf_skb_duplicated);

    e = get_entry(table_base, private->hook_entry[hook]);

    do {
        const struct xt_entry_target *t;
        const struct xt_entry_match *ematch;
        struct xt_counters *counter;

        IP_NF_ASSERT(e);
        /*

   匹配IP包,成功則繼續匹配下去,不然跳到下一個規則  

   ip_packet_match匹配標準match, 也就是ip報文中的一些基本的元素,如來源/目的地址,進/出網口,協議等,由於要匹配的內容是固定的,因此具體的函數實現也是固定的。

   而IPT_MATCH_ITERATE (應該猜到實際是調用第二個參數do_match函數)匹配擴展的match,如字符串匹配,p2p匹配等,由於要匹配的內容不肯定,因此函數的實現也是不同的,因此do_match的實現就和具體的match模塊有關了。 

   這裏的&e->ip就是上面的ipt_ip結構

*/
        if (!ip_packet_match(ip, indev, outdev,
            &e->ip, acpar.fragoff)) {//遍歷匹配match
 no_match:
            e = ipt_next_entry(e);
            continue;
        }

        xt_ematch_foreach(ematch, e) {
            acpar.match     = ematch->u.kernel.match;
            acpar.matchinfo = ematch->data;
            if (!acpar.match->match(skb, &acpar))
                goto no_match;
        }

        counter = xt_get_this_cpu_counter(&e->counters);
        ADD_COUNTER(*counter, skb->len, 1);
/* ipt_get_target獲取當前target,t是一個ipt_entry_target結構,這個函數就是簡單的返回e+e->target_offset

每一個entry只有一個target,因此不須要像match同樣遍歷,直接指針指過去了*/
        t = ipt_get_target(e);
        IP_NF_ASSERT(t->u.kernel.target);

#if IS_ENABLED(CONFIG_NETFILTER_XT_TARGET_TRACE)
        /* The packet is traced: log it */
        if (unlikely(skb->nf_trace))
            trace_packet(state->net, skb, hook, state->in,
                     state->out, table->name, private, e);
#endif
/* 這裏都仍是和擴展的match的匹配很像,可是下面一句

有句註釋:Standard target? 判斷當前target是否標準的target?

而判斷的條件是u.kernel.target->target,就是ipt_target結構裏的target函數是否爲空,
而下面還出現了ipt_standard_target結構和verdict變量,好吧,先停下,看看ipt_standard_target結構再說 
ipt_standard_target的定義:
struct ipt_standard_target
{
     struct ipt_entry_target target;
      int verdict;
};
也就比ipt_entry_target多了一個verdict(判斷),請看前面的nf_hook_slow()函數,裏面也有verdict變量,
用來保存hook函數的返回值,常見的有這些
#define NF_DROP 0
#define NF_ACCEPT 1
#define NF_STOLEN 2
#define NF_QUEUE 3
#define NF_REPEAT 4
#define RETURN     IPT_RETURN
#define IPT_RETURN     (-NF_MAX_VERDICT - 1)
#define NF_MAX_VERDICT NF_REPEAT 
咱們知道chain(鏈)是某個檢查點上檢查的規則的集合。除了默認的chain外,用戶還能夠建立新的chain。在iptables中,
同一個chain裏的規則是連續存放的。默認的chain的最後一條規則的target是chain的policy。用戶建立的chain的最後一條
規則的target的調用返回值是NF_RETURN,遍歷過程將返回原來的chain。規則中的target也能夠指定跳轉到某個用戶建立的chain上,
這時它的target是ipt_stardard_target,而且這個target的verdict值大於0。若是在用戶建立的chain上沒有找到匹配的規則,
遍歷過程將返回到原來chain的下一條規則上。事實上,target也是分標準的和擴展的,但前面說了,畢竟一個是條件,一個是動做,
target的標準和擴展的關係和match仍是不太同樣的,不能一律而論,並且在標準的target裏還能夠根據verdict的值再
劃分爲內建的動做或者跳轉到自定義鏈簡單的說,標準target就是內核內建的一些處理動做或其延伸
擴展的固然就是徹底由用戶定義的處理動做
*/

        if (!t->u.kernel.target->target) {
            int v;

            v = ((struct xt_standard_target *)t)->verdict;
            /*v小於0,動做是默認內建的動做,也多是自定義鏈已經結束而返回return標誌*/
            if (v < 0) { /*若是v大於0,記錄是跳轉偏移量,小於0,是標準target*/
                /* Pop from stack? */
                if (v != XT_RETURN) {
                    verdict = (unsigned int)(-v) - 1;
                    break;
                }/* e和back分別是當前表的當前Hook的規則的起始偏移量和上限偏移量,即entry的頭和尾,e=back */
                if (stackidx == 0) {
                    e = get_entry(table_base,
                        private->underflow[hook]);
                } else {
                    e = jumpstack[--stackidx];
                    e = ipt_next_entry(e);
                }
                continue;
            }
            /* v大於等於0,處理用戶自定義鏈,若是當前鏈後還有規則,而要跳到自定義鏈去執行,那麼須要保存一個back點,
            以指示程序在匹配完自定義鏈後,應當繼續匹配的規則位置,天然地, back點應該爲當前規則的下一條規則(若是存在的話)
至於爲何下一條規則的地址是table_base+v, 就要去看具體的規則是如何添加的了 */
            if (table_base + v != ipt_next_entry(e) &&
                !(e->ip.flags & IPT_F_GOTO))
                jumpstack[stackidx++] = e;

            e = get_entry(table_base, v); /*根據verdict的偏移量找到跳轉的rule*/
            continue;
        }

        acpar.target   = t->u.kernel.target;
        acpar.targinfo = t->data; /*若是是擴展target,就執行擴展targe的target處理函數*/

        verdict = t->u.kernel.target->target(skb, &acpar);
        /* Target might have changed stuff. */
        ip = ip_hdr(skb);
        if (verdict == XT_CONTINUE)
            e = ipt_next_entry(e);
        else
            /* Verdict */
            break;
    } while (!acpar.hotdrop);

    xt_write_recseq_end(addend);
    local_bh_enable();

    if (acpar.hotdrop)
        return NF_DROP;
    else return verdict;
}

 

filter回調函數 和 match、target之間的關係:this

相關文章
相關標籤/搜索