HOWTO: Linux Netfilter Hook Function Developement

我是從上研究所以後才開始接觸 Linux 的,那時候為了強迫自己盡速上手,把我家一台古董級電腦搬出來,按著「施銘威研究室」的 Linux 系列進行一個口令一個動作的學習(回首那段日子 ... 真是 ...)。而我第一個學的操作就是 iptables 的操作,理由很簡單,因為那時候我待在「Distributed Computing and Network Security Laboratory」,想說不會操作防火牆不是太丟臉了嗎?從此就開始接觸到 Linux Kernel 裡面很重要的一個封包處理機制 Netfilter。

Netfilter 是 Linux Kernel 2.4 和 2.6 的網路封包處理框架,下面是官網的介紹:

netfilter is a set of hooks inside the Linux kernel that allows kernel modules to register callback functions with the network stack. A registered callback function is then called back for every packet that traverses the respective hook within the network stack. (from http://www.netfilter.org/)

簡單來說,我們可以把 Netfilter 的機制想成下面的圖:


圖中的五個橢圓形指的就是五個 Hook Points。而旁邊一串紅色的小圈圈代表的是在該 Kook Point 上的一串規則。凡是有網路封包經過一 Hook Point 時就會進入該 Hook Point 所掛載的所有規則。

我們利用 linux kernel 2.6.28.7 來搜尋一下 NF_HOOK 這個關鍵字:

ip_forward.c: ip_forward, NF_INET_FORWARD
ip_input.c: ip_local_deliver, NF_INET_LOCAL_IN
ip_input.c: ip_rcv, NF_INET_PRE_ROUTING
ip_output.c: ip_mc_output, NF_INET_POST_ROUTING
ip_output.c: ip_mc_output, NF_INET_POST_ROUTING
ip_output.c: ip_mc_output, NF_INET_POST_ROUTING
ip_output.c: ip_output, NF_INET_POST_ROUTING
raw.c: raw_send_hdrinc, NF_INET_LOCAL_OUT

而 Hook Point 的定義則是在

include/linux/netfilter.h

enum nf_inet_hooks {
NF_INET_PRE_ROUTING,
NF_INET_LOCAL_IN,
NF_INET_FORWARD,
NF_INET_LOCAL_OUT,
NF_INET_POST_ROUTING,
NF_INET_NUMHOOKS
};

這樣我們就很輕易的找到了 IPv4 裡面的 Hook Points。至於 NF_HOOK 之後的程式碼運作,相關文章請見「Netfilter 的 Hook 方式初探」。在這裡要特別提出一件事情, Linux Kernel Netfilter 的架構裡面兩個概念,一個是 Chains ,指的就是每個 Hook Points 上面的那一串封包處理函式;另一個就是 Tables,在 Netfilter 裡面總共有三種,包含了 filter、nat、mangle。每個 Tables 都有負責管轄到的 Chains,舉例來說,filter 就只能管理 INPUT/OUTPUT/FORWARD 三個 Hook Points 上面的 Chains。那麼像 nat 和 filter 同時管到了 OUTPUT 這個 Hook Point 時,哪一個 Table 的優先權比較高呢??這邊就是我要講得重點了 ... 基本上 Table 不牽涉到同一個 Hook Point 上封包處理函式的優先順序,這部份都是由函式在註冊時的所被賦予的優先順序來決定的!簡單來說,Tables 感覺只是方便人操作的一種概念,真正去處理封包的還是那一串被掛載的函式。那為甚麼有人會說某一 Table 的優先權比較高呢?那是因為下面的定義:

include/linux/netfilter_ipv4.h

enum nf_ip_hook_priorities {
NF_IP_PRI_FIRST = INT_MIN,
NF_IP_PRI_CONNTRACK_DEFRAG = -400,
NF_IP_PRI_RAW = -300,
NF_IP_PRI_SELINUX_FIRST = -225,
NF_IP_PRI_CONNTRACK = -200,
NF_IP_PRI_MANGLE = -150,
NF_IP_PRI_NAT_DST = -100,
NF_IP_PRI_FILTER = 0,
NF_IP_PRI_SECURITY = 50,
NF_IP_PRI_NAT_SRC = 100,
NF_IP_PRI_SELINUX_LAST = 225,
NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX,
NF_IP_PRI_LAST = INT_MAX,
};

之所以會特別提出這件事情,是因為如果我們有想要加上任何的「封包處理函式」時,其實我們可以不必理會 Table 這個概念,直接把寫好的東西掛載上去即可!所以在接下來的文章裡面,我們不會提到如何加上新的 Table,而是單純的增加新的封包處理規則在適當的 Hook Point 上面。(因為加上新的 Table 看起來有點麻煩,所以下次再說)

接下來我會試著介紹一下如何設計掛在 Hook Point 上面的封包處理模組,這才是本文的重點(所以前面都算是廢話加前言 :p )。首先,來看看如何註冊一個新的封包處理函式:

include/linux/netfilter.h

int nf_register_hook(struct nf_hook_ops *reg);
int nf_register_hooks(struct nf_hook_ops *reg, unsigned int n);

看起來應該很容易理解用法,接下來看看那個重要的 structure ...

include/linux/netfilter.h

struct nf_hook_ops
{
struct list_head list;

/* User fills in from here down. */
nf_hookfn *hook;// 最重要的部份,封包處理函式
struct module *owner;
u_int8_t pf;//PF_INET
unsigned int hooknum;//Hook Point
/* Hooks are ordered in ascending priority. */
int priority;//Priority
};

net/netfilter/core.c

int nf_register_hook(struct nf_hook_ops *reg)
{
@@@@struct nf_hook_ops *elem;
@@@@int err;

@@@@err = mutex_lock_interruptible(&nf_hook_mutex);
@@@@if (err < style="color: rgb(255, 255, 255);">@@@@@@@@return err;
@@@@list_for_each_entry(elem, &nf_hooks[reg->pf][reg->hooknum], list)
@@@@{
@@@@@@@@if (reg->priority < elem->priority)
@@@@@@@@@@@@break;
@@@@}
@@@@list_add_rcu(&reg->list, elem->list.prev);
@@@@mutex_unlock(&nf_hook_mutex);
@@@@return 0;
}

非常非常的簡單。問題是 Hook 的程式要怎麼寫?來看看下面的程式:

net/ipv4/netfilter/iptable_filter.c

static struct nf_hook_ops ipt_ops[] __read_mostly = {
{
@@@@.hook = ipt_local_in_hook,
@@@@.owner = THIS_MODULE,
@@@@.pf = PF_INET,
@@@@.hooknum = NF_INET_LOCAL_IN,
@@@@.priority = NF_IP_PRI_FILTER,
},
{
@@@@.hook = ipt_hook,
@@@@.owner = THIS_MODULE,
@@@@.pf = PF_INET,
@@@@.hooknum = NF_INET_FORWARD,
@@@@.priority = NF_IP_PRI_FILTER,
},
{
@@@@.hook = ipt_local_out_hook,
@@@@.owner = THIS_MODULE,
@@@@.pf = PF_INET,
@@@@.hooknum = NF_INET_LOCAL_OUT,
@@@@.priority = NF_IP_PRI_FILTER,
}
};

很好,有點頭緒了,繼續看下去!

net/ipv4/netfilter/iptable_filter.c

static unsigned int
ipt_hook( unsigned int hook,
@@@@@@struct sk_buff *skb,
@@@@@@const struct net_device *in,
@@@@@@const struct net_device *out,
@@@@@@int (*okfn)(struct sk_buff *))//默認處理函式
{
@@@@@@return ipt_do_table(skb, hook, in, out,
@@@@@@@@@@@@dev_net(in)->ipv4.iptable_filter);
}

這裡我們看到了一個 Hook 函式所需要的參數,包含了 Hook Point, 經過的封包,In/Out Device,以及默認處理函式(這邊說明一下,其實我根本就不知道 okfn 是幹麼的,因為從程式裡面根本看不出來,這純粹是網路上查到的)。所以我們要寫的話,就是提供一個這樣的 Hook Function 並包在 struct nf_hook_ops 裡面進行註冊就好了。

在回傳值的部份,定義在下面的地方:

include/linux/netfilter.h

#define NF_DROP 0
#define NF_ACCEPT 1
#define NF_STOLEN 2
#define NF_QUEUE 3
#define NF_REPEAT 4
#define NF_STOP 5
#define NF_MAX_VERDICT NF_STOP

最後,來看看 ipt_do_table:

net/ipv4/netfilter/ip_tables.c

... 好多 ... 下次再說吧
看完這一篇以後,應該可以很輕鬆的寫出一個 Hook 來才是
但其實這樣還不是正解,正確的作法,
應該要寫在現有的 Table 裡面才是... 不過... 再說吧

附錄(一個簡單的範例) by Samlin
static struct nf_hook_ops _nfho;

unsigned int _hookFunc(unsigned int _hooknum,
@@@@@@@@@@@@struct sk_buff **_pSkb,
@@@@@@@@@@@@const struct net_device *_pIn,
@@@@@@@@@@@@const struct net_device *_pOut,
@@@@@@@@@@@@int (*_pOkfn)(struct sk_buff *))
{
printk(KERN_INFO "Get a packet\n");
return NF_ACCEPT;
}

int _initHook(void)
{
@@@@_nfho.hook = _gtpu_hookFunc;
@@@@_nfho.hooknum = NF_IP_LOCAL_IN;
@@@@_nfho.pf = PF_INET;
@@@@_nfho.priority = NF_IP_PRI_FIRST;
@@@@nf_register_hook(&_nfho);
@@@@return 0;
}

void _uninitHook(void)
{
@@@@nf_unregister_hook(&_nfho);
}

留言

這個網誌中的熱門文章

我弟家的新居感恩禮拜分享:善頌善禱

如何將Linux打造成OpenFlow Switch:Openvswitch

Openssl 範例程式:建立SSL連線