自己寫 Netfilter 匹配器

來源:互聯網
上載者:User
概述

寫一個 iptables/netfilter
匹配模組的大體步驟如下:

  • 找到你要匹配的具體情況。
  • 寫用於接受參數的使用者空間部分程式。
  • 寫用於分析包資訊,得出是否匹配結論的核心空間部分程式。

 

1.0 iptables 模組

iptables 庫的用途基本上講就是和使用者互動,它捕獲使用者要傳送給核心態程式的參數。  

1.1 可用的資料結構和函數

首先是一些基本資料結構。
<iptables/include/iptables.h>
稍後文中就將可以看到這些結構的用途了。

/* Include file for additions: new matches and targets. */
struct iptables_match
{
struct iptables_match *next;

ipt_chainlabel name;

const char *version;

/* Size of match data. */
size_t size;

/* Size of match data relevent for userspace comparison purposes */
size_t userspacesize;

/* Function which prints out usage message. */
void (*help)(void);

/* Initialize the match. */
void (*init)(struct ipt_entry_match *m, unsigned int *nfcache);

/* Function which parses command options; returns true if it
ate an option */
int (*parse)(int c, char **argv, int invert, unsigned int *flags,
const struct ipt_entry *entry,
unsigned int *nfcache,
struct ipt_entry_match **match);

/* Final check; exit if not ok. */
void (*final_check)(unsigned int flags);

/* Prints out the match iff non-NULL: put space at end */
void (*print)(const struct ipt_ip *ip,
const struct ipt_entry_match *match, int numeric);

/* Saves the match info in parsable form to stdout. */
void (*save)(const struct ipt_ip *ip,
const struct ipt_entry_match *match);

/* Pointer to list of extra command-line options */
const struct option *extra_opts;

/* Ignore these men behind the curtain: */
unsigned int option_offset;
struct ipt_entry_match *m;
unsigned int mflags;
#ifdef NO_SHARED_LIBS
unsigned int loaded; /* simulate loading so options are merged properly */
#endif
};

 

1.2 深入骨架程式

 

1.2.1 初始化

我們首先初始化 'iptables_match' 結構中的常用欄位:

static struct iptables_match ipaddr
= {

'Name'
是你的函數庫的檔案名稱(也就是 libipt_ipaddr)。
你不能在這個位置放其它的東西,這是用來自動載入你的庫的。

    .name            = "ipaddr",

下一個欄位 'version' 是
iptables 的版本。後面的兩個欄位都是用於保持使用者態程式和核心態共用結構的大小一致性的。

    .version         = IPTABLES_VERSION,
.size = IPT_ALIGN(sizeof(struct ipt_ipaddr_info)),
.userspacesize = IPT_ALIGN(sizeof(struct ipt_ipaddr_info)),

'Help'
是使用者輸入 'iptables -m module -h' 的時候要調用的函數。'Parse'
是使用者輸入一條新規則的時候調用的,用於驗證參數的合法性。'print' 就是使用 'iptables -L' 的時候顯示前面添加的規則的。

    .help            = &help,
.init = &init,
.parse = &parse,
.final_check = &final_check,
.print = &print,
.save = &save,
.extra_opts = opts
};

iptables
架構能夠支援多個共用庫。每個共用庫必須使用 <iptables/iptables.c> 中定義的 'register_match()'
向 iptables 註冊。這個函數將在模組被 iptables 載入的時候調用。 更多資訊請參考:'man dlopen'。

void _init(void)
{
register_match(&ipaddr);
}

 

1.2.2 save 函數

如果我們有一個需要儲存的規則集,可以利用 iptables 提供的工具
'iptables-save',它可以儲存下所有的規則。顯然你需要擴充這個工具來儲存下來這些規則。這個擴充通過 save 函數完成。

static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match)
{
const struct ipt_ipaddr_info *info = (const struct ipt_ipaddr_info *)match->data;

如果源地址是規則的一部分的話,列印它。

   if (info->flags & IPADDR_SRC) {
if (info->flags & IPADDR_SRC_INV)
printf("! ");
printf("--ipsrc ");
print_ipaddr((u_int32_t *)&info->ipaddr.src);
}

如果目的地址是規則的一部分的話就列印目的地址。

   if (info->flags & IPADDR_DST) {
if (info->flags & IPADDR_DST_INV)
printf("! ");
printf("--ipdst ");
print_ipaddr((u_int32_t *)&info->ipaddr.dst);
}
}

 

1.2.3 print 函數

和上面的 save 所蘊含的哲學一樣,也有一個 print 函數用於列印規則。它在 'iptables -L'
的時候被調用。我們將在下文裡看到參數 'ipt_entry_match *match' 的用途,不過我們已經對它有了一點概念了,是吧?

static void print(const struct ipt_ip *ip,
const struct ipt_entry_match *match,
int numeric)
{
const struct ipt_ipaddr_info *info = (const struct ipt_ipaddr_info *)match->data;

if (info->flags & IPADDR_SRC) {
printf("src IP ");
if (info->flags & IPADDR_SRC_INV)
printf("! ");
print_ipaddr((u_int32_t *)&info->ipaddr.src);
}

if (info->flags & IPADDR_DST) {
printf("dst IP ");
if (info->flags & IPADDR_DST_INV)
printf("! ");
print_ipaddr((u_int32_t *)&info->ipaddr.dst);
}
}

 

1.2.4 final check 函數

這個函數是最後一次正確性檢查的機會。它在使用者輸入完規則之後、參數解析剛剛完成的時候被調用。

static void final_check(unsigned int flags)
{
if (!flags)
exit_error(PARAMETER_PROBLEM, "ipt_ipaddr: Invalid parameters.");
}

 

1.2.5 parse 函數

parse
是最重要的一個函數,因為這裡要檢查參數的正確性,並寫入我們將共用給核心態程式的資訊。它在每次參數被發現的時候被調用,也就是說,如果使用者輸入了兩個參數,這個函數就將被以不同的參數代碼
c 調用兩次。

static int parse(int c, char **argv, int invert, unsigned int *flags,
const struct ipt_entry *entry,
unsigned int *nfcache,
struct ipt_entry_match **match)
{

我們使用特殊結構來儲存我們要傳遞給核心態程式的資訊。'match'
指標被傳遞給多個函數,我們可以每次使用同樣的資料結構。一旦規則被載入了,這個指標就被複製到了核心態程式裡。通過這個方式,核心模組可以知道使用者想要分析什麼(這正是問題的關鍵,不是麼?)。

   struct ipt_ipaddr_info *info = (struct ipt_ipaddr_info *)(*match)->data;

每個參數對應著一個單獨的值,於是我們能根據進入的參數決定採取何種行動。下文中我們將看到我們如何把參數變成數值。

   switch(c) {

首先,我們檢查參數是否被使用了多次。如果使用了多次的話,調用
<iptables/iptables.c> 中定義的 'exit_error()' 函數,這樣程式會立刻帶著
<iptables/include/iptables_common.h> 中定義的 'PARAMETER_PROBLEM'
的錯誤狀態推出。否則,我們在我們的標頭檔中定義的 'IPADDR_SRC' 中設定 'flags' 和
'info->flags'。稍後我們將介紹這個標頭檔。

雖然這兩個標誌看起來差不多,但是是完全不同的。'flag'
的範圍就是這個函數,而 'info->flags' 是我們用於和核心態程式共用資訊的結構的一部分。

      case '1':
if (*flags & IPADDR_SRC)
exit_error(PARAMETER_PROBLEM, "ipt_ipaddr: Only use --ipsrc once!");
*flags |= IPADDR_SRC;
info->flags |= IPADDR_SRC;

檢查如果取反標誌
'!' 是否存在,如果有的話,在 'info->flags' 中寫相應的值。
之後調用為這個骨架程式所寫的內建函式 'parse_ipaddr'
來把 IP 位址從字串轉化為 32 位值。

         if (invert)
info->flags |= IPADDR_SRC_INV;

parse_ipaddr(argv[optind-1], &info->ipaddr.src);
break;

同樣考慮,我們檢查是否存在多次設定,置恰當的標誌。

      case '2':
if (*flags & IPADDR_DST)
exit_error(PARAMETER_PROBLEM, "ipt_ipaddr: Only use --ipdst once!");
*flags |= IPADDR_DST;
info->flags |= IPADDR_DST;
if (invert)
info->flags |= IPADDR_DST_INV;

parse_ipaddr(argv[optind-1], &info->ipaddr.dst);
break;

default:
return 0;
}

return 1;
}

 

1.2.6 options 結構

前文中,我們已經談到了要將每個參數映射到一個值。 'struct option'
就是一個達到這個目的的好辦法。要想得到關於這個結構的進一步資訊,強烈建議閱讀 'man 3 getopt'。

static struct option opts[] = {
{ .name = "ipsrc", .has_arg = 1, .flag = 0, .val = '1' },
{ .name = "ipdst", .has_arg = 1, .flag = 0, .val = '2' },
{ .name = 0 }
};

 

1.2.7 init 函數

init 函數用於初始化一些特定的東西,比如 netfilter 的 cache
系統。現在不必過多考慮這個函數的具體用途。

static void init(struct ipt_entry_match *m, unsigned int *nfcache)
{
/* Can't cache this */
*nfcache |= NFC_UNKNOWN;
}

 

1.2.7 help 函數

這個函數通過 'iptables -m match_name -h' 被調用,用於顯示可用的參數。

static void help(void)
{
printf (
"IPADDR v%s options:/n"
"[!] --ipsrc /t/t The incoming ip addr matches./n"
"[!] --ipdst /t/t The outgoing ip addr matches./n"
"/n", IPTABLES_VERSION
);
}

 

1.2.8 標頭檔 'ipt_ipaddr.h'

這個檔案定義了我們需要的一些東西。

#ifndef _IPT_IPADDR_H
#define _IPT_IPADDR_H

我們已經在上文中使用了這些特定的值了。

#define IPADDR_SRC   0x01     /* Match source IP addr */
#define IPADDR_DST 0x02 /* Match destination IP addr */

#define IPADDR_SRC_INV 0x10 /* Negate the condition */
#define IPADDR_DST_INV 0x20 /* Negate the condition */

結構
'ipt_ipaddr_info' 是將要被拷貝到核心態程式的那個資料結構。

struct ipt_ipaddr {
u_int32_t src, dst;
};

struct ipt_ipaddr_info {

struct ipt_ipaddr ipaddr;

/* Flags from above */
u_int8_t flags;

};

#endif

 

1.3 第一章小結

第一部分中,我們討論了 iptables 庫的作用。我們記述了每個函數的內容和 'ipt_ipaddr_info'
這個用於儲存資訊的將要被拷貝到核心態程式來做進一步處理的重要結構。我們也看到了 iptables
結構和如何註冊一個新的庫。
應該注意,這僅僅是一個用於示範架構如何工作的骨架程式。而且,'ipt_ipaddr_info' 和其他類似的東西並不是
iptables/netfilter 的一部分,而僅僅是這個例子的一部分。  

2.0 netfilter 模組

一個匹配模組的工作就是察看每一個收到的包並決定是否符合某個判決準則。這個模組要做如下工作:

  • 接收每個包,並察看匹配模組相關的表
  • 告知 netfilter,我們的模組是否匹配上了這個包

 

2.1 可用的函數與資料結構

首先是一些基本資料結構,這些資料結構定義在
<linux/netfilter_ipv4/ip_tables.h>。
如果你對這個結構以及前面的 iptables
部分還有興趣的話,你可以看看 Rusty Russell 和 Harald Welte 寫的 netfilter hacking howto 。

struct ipt_match
{
struct list_head list;

const char name[IPT_FUNCTION_MAXNAMELEN];

/* Return true or false: return FALSE and set *hotdrop = 1 to
force immediate packet drop. */
/* Arguments changed since 2.4, as this must now handle
non-linear skbs, using skb_copy_bits and
skb_ip_make_writable. */
int (*match)(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset,
int *hotdrop);

/* Called when user tries to insert an entry of this type. */
/* Should return true or false. */
int (*checkentry)(const char *tablename,
const struct ipt_ip *ip,
void *matchinfo,
unsigned int matchinfosize,
unsigned int hook_mask);

/* Called when entry of this type deleted. */
void (*destroy)(void *matchinfo, unsigned int matchinfosize);

/* Set this to THIS_MODULE. */
struct module *me;
};

 

2.2 深入骨架程式

 

2.2.1 初始化

首先,我們初始化 'ipt_match' 資料結構中的常用域。

static struct ipt_match ipaddr_match
= {

'name'
是你的模組的檔案名稱字串(也就是說 ipt_ipaddr)。

.name       = "ipaddr",

下面的欄位是架構將要使用的回呼函數.'match'是當一個包傳送給你的模組的時候要調用的函數.

.match      = match,
.checkentry = checkentry,
.me = THIS_MODULE,
};

你的核心模組的
init 函數需要通過指向一個 'struct ipt_match' 的指標調用 'ipt_register_match()' 來向 netfilter
架構註冊.這個函數在模組被載入的時候調用.

static int __init init(void)
{
printk(KERN_INFO "ipt_ipaddr: init!/n");
return ipt_register_match(&ipaddr_match);
}

當把模組從核心中移出的時候這個函數會被調用.這裡我們進行的工作是登出匹配器。

static void __exit fini(void)
{
printk(KERN_INFO "ipt_ipaddr: exit!/n");
ipt_unregister_match(&ipaddr_match);
}

設定讓這兩個函數在模組裝入和移出的時候被調用。

module_init(init);
module_exit(fini);

 

2.2.2 match 函數

Linux 的 TCP/IP 協議棧包括5個 netfilter
鉤子。這樣,一個包近來之後,協議棧把包送到相應的鉤子,依次進入每個表,再依次疊帶每條規則。當你的模組得到包的時候,你的模組就可以進行它的工作了。

static int match(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset,
const void *hdr,
u_int16_t datalen,
int *hotdrop)
{

希望你還記著我們在使用者態程式裡面做了些什麼!
:)。現在把使用者態程式拷貝過來的資料結構映射到我們這裡

const struct ipt_skeleton_info *info = matchinfo;

'skb'
包含了我們想要處理的包。想要得到關於這個在 linux 的 TCP/IP 協議棧中到處都是功能強大的資料結構的資訊,可以看看 Harald Welte
寫的一出色的文章 article (ftp://ftp.gnumonks.org/pub/doc/skb-doc.html) 。

   struct iphdr *iph = skb->nh.iph;

這裡,我們就是列印一些有趣的東西來看看他們長成什麼樣子。宏
'NIPQUAD' 用於以可讀的方式顯示一個 IP 位址,它是在 <linux/include/linux/kernel.h>
中定義的。

   printk(KERN_INFO "ipt_ipaddr: IN=%s OUT=%s TOS=0x%02X "
"TTL=%x SRC=%u.%u.%u.%u DST=%u.%u.%u.%u "
"ID=%u IPSRC=%u.%u.%u.%u IPDST=%u.%u.%u.%u/n",

in ? (char *)in : "", out ? (char *)out : "", iph->tos,
iph->ttl, NIPQUAD(iph->saddr), NIPQUAD(iph->daddr),
ntohs(iph->id), NIPQUAD(info->ipaddr.src), NIPQUAD(info->ipaddr.dst)
);

如果輸入了
'--ipsrc' 參數,我們察看源地址是否和規則指定的地址相匹配。別忘了考慮反標誌 '!'。如果沒有匹配,我們返回 0.

   if (info->flags & IPADDR_SRC) {
if ( (ntohl(iph->saddr) != ntohl(info->ipaddr.src)) ^ !!(info->flags & IPADDR_SRC_INV) ) {

printk(KERN_NOTICE "src IP %u.%u.%u.%u is not matching %s./n",
NIPQUAD(info->ipaddr.src),
info->flags & IPADDR_SRC_INV ? " (INV)" : "");
return 0;
}
}

這裡,我們進行完全相同的工作,只是察看
'--ipdst' 參數。

   if (info->flags & IPADDR_DST) {
if ( (ntohl(iph->daddr) != ntohl(info->ipaddr.dst)) ^ !!(info->flags & IPADDR_DST_INV) ) {

printk(KERN_NOTICE "dst IP %u.%u.%u.%u is not matching%s./n",
NIPQUAD(info->ipaddr.dst),
info->flags & IPADDR_DST_INV ? " (INV)" : "");
return 0;
}
}

如果都不成功,返回
1,表明我們匹配了這個包。

   return 1;
}

 

2.2.3 checkentry 函數

checkentry 通常是最後一次合法性檢查的機會。關於它何時被調用有些難以理解。看看 post
(http://www.mail-archive.com/netfilter-devel@lists.samba.org/msg00625.html)
作為一個解釋吧。這篇文章也是一篇 netfilter hacking howto。

static int checkentry(const char *tablename,
const struct ipt_ip *ip,
void *matchinfo,
unsigned int matchsize,
unsigned int hook_mask)
{
const struct ipt_skeleton_info *info = matchinfo;

if (matchsize != IPT_ALIGN(sizeof(struct ipt_skeleton_info))) {
printk(KERN_ERR "ipt_skeleton: matchsize differ, you may have forgotten to recompile me./n");
return 0;
}

printk(KERN_INFO "ipt_skeleton: Registered in the %s table, hook=%x, proto=%u/n",
tablename, hook_mask, ip->proto);

return 1;
}

 

2.3 第二章小結

在第二部分,我們講了 netfilter
模組以及如何使用特定結構註冊它。另外我們還討論了如何根據使用者空間部分給出的判據匹配特定的情況。  

3.0 運行 iptables/netfilter

我們已經看到了如何寫一個新的 iptables/netfilter
匹配模組。現在我們將把它添加到核心中來運行它。這裡,我假設你知道如何編譯核心。首先把骨架匹配檔案從本文下載頁面下載下來。  

3.1 iptables

現在,如果你還沒有 iptables 的原始碼的話,可以從 ftp://ftp.netfilter.org/pub/iptables/ 下載。然後拷貝
'libipt_ipaddr.c' 到 <iptables/extensions/>。

這是
<iptables/extensions/Makefile> 中的一行,你應該加上 'ipaddr'。

PF_EXT_SLIB:=ah addrtype comment connlimit connmark conntrack dscp ecn
esp hashlimit helper icmp iprange length limit ipaddr mac mark
multiport owner physdev pkttype realm rpc sctp standard state tcp tcpmss
tos ttl udp unclean CLASSIFY CONNMARK DNAT DSCP ECN LOG MARK MASQUERADE
MIRROR NETMAP NOTRACK REDIRECT REJECT SAME SNAT TARPIT TCPMSS TOS TRACE
TTL ULOG

 

3.2 核心

首先,你應該拷貝 'ipt_ipaddr.c' 到
<linux/net/ipv4/netfilter/>,拷貝 ' ipt_ipaddr.h' 到
<linux/net/ipv4/netfilter/>。有些讀者可能還在使用 2.4 核心,所以我同時提供了 2.4和 2.6
的檔案。

對於 2.4 核心,編輯 <linux/net/ipv4/netfilter/Config.in>
,加入下面加重的行。

# The simple matches.
dep_tristate ' limit match support' CONFIG_IP_NF_MATCH_LIMIT $CONFIG_IP_NF_IPTABLES
dep_tristate ' ipaddr match support' CONFIG_IP_NF_MATCH_IPADDR $CONFIG_IP_NF_IPTABLES

然後,編輯 <linux/Documentation/Configure.help>
加入加重的行。我複製了一些文本來協助你找到要加入內容的地方。

limit match support
CONFIG_IP_NF_MATCH_LIMIT
limit matching allows you to control the rate at which a rule can be
...
ipaddr match support
CONFIG_IP_NF_MATCH_IPADDR
ipaddr matching. etc etc.

最後,你必須把加重的行加入到 <linux/net/ipv4/netfilter/Makefile> 之中。

# matches
obj-$(CONFIG_IP_NF_MATCH_HELPER) += ipt_helper.o
obj-$(CONFIG_IP_NF_MATCH_LIMIT) += ipt_limit.o
obj-$(CONFIG_IP_NF_MATCH_IPADDR) += ipt_ipaddr.o

Now for 2.6, files to edit are
<linux/net/ipv4/netfilter/Kconfig> and
<linux/net/ipv4/netfilter/Makefile>. 對 2.6 核心,編輯的檔案應該是
<linux/net/ipv4/netfilter/Kconfig>和
<linux/net/ipv4/netfilter/Makefile>。  

總結

下面剩下的的就是從新編譯以及我忘了說的了。
Happy hacking!!
感謝 Samuel Jean.  

 

  原文地址 http://www.tldp.org/linuxfocus/ChineseGB/February2005/article367.shtml

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.