自訂iptables/netfilter的目標模組

來源:互聯網
上載者:User
本文檔的Copyleft歸yfydz所有,使用GPL發布,可以自由拷貝,轉載,轉載時請保持文檔的完整性,嚴禁用於任何商業用途。
msn: yfydz_no1@hotmail.com

來源: http://yfydz.cublog.cn1. 前言Linux中的netfilter提供了一個防火牆架構,具有很好的擴充性,除了內建的模組之外,使用者可以根據自己的需求定義新的防火牆模組加
入其中,而編程過程也不是很複雜,只要依葫蘆畫瓢即可,可在原來的類似功能的模組基礎上修改即可,甚至對各函數是如何調用,一些內部結構是如何定義的都不
用詳細瞭解,本文即介紹如何編寫自訂的目標模組。 目標(target)是防火牆規則策略中的結果部分,定義對資料包要進行如何處理,如接受、丟棄、修改、繼續等。在具體實現時分為兩部分,核心
部分和使用者空間部分:核心中是以netfilter的擴充模組實現,定義該模組並將其掛接到netfilter的目標鏈表中後,在核心進行目標檢測時自動
根據目標名稱尋找該模組而調用相應的目標函數,完成真正的目標功能;在使用者空間,目標模組是作為iptables的一個擴充動態庫來實現,只是一個使用者接
口,不完成實際目標功能,完成接收使用者輸入並將目標資料結構傳遞到核心的功能,該庫的名稱有限制,必須為libipt_xxx.so,其中“xxx”是該
目標的名字,為區別於匹配,通常目標的名稱一般都是用大寫。2. 核心模組目標在2.4核心和在2.6核心中的函數參數略有區別,兩者不相容,但只要簡單修改後即可相互移植,主體部分不需要改變,本文以2.6核心的模組為例。 為方便說明,還是通過舉例來進行說明,要實現的目標是修改IP頭中的ID欄位,在實際使用中這個功能是沒什麼實際意義的,只是用來舉例說明。 在核心中已經內建了net/ipv4/netfilter/ipt_ID.c模組用來修改IP頭中的ID欄位,我們可以以此模組為基礎來進行修改。 首先要定義要進行目標的資料結構,用來描述目標條件,以ipt_ID.h標頭檔為基礎修改為:/* include/linux/netfilter_ipv4/ipt_ID.h */
#ifndef _IPT_ID_H_target
#define _IPT_ID_H_targetstruct ipt_id_target_info {
 /* network order. */
 u_int16_t id;  // id是16位的數
};#endif /*_IPT_ID_H_target*/ 然後是定義核心模組處理,最主要的是定義一個ipt_target目標結構,該結構在include/linux/netfilter_ipv4/ip_tables.h中定義: /* Registration hooks for targets. */
struct ipt_target
{
 struct list_head list; const char name[IPT_FUNCTION_MAXNAMELEN]; /* Called when user tries to insert an entry of this type:
           hook_mask is a bitmask of hooks from which it can be
           called. */
 /* Should return true or false. */
 int (*checkentry)(const char *tablename,
     const struct ipt_entry *e,
     void *targinfo,
     unsigned int targinfosize,
     unsigned int hook_mask); /* Called when entry of this type deleted. */
 void (*destroy)(void *targinfo, unsigned int targinfosize); /* Returns verdict.  Argument order changed since 2.4, as this
           must now handle non-linear skbs, using skb_copy_bits and
           skb_ip_make_writable. */
 unsigned int (*target)(struct sk_buff **pskb,
          const struct net_device *in,
          const struct net_device *out,
          unsigned int hooknum,
          const void *targinfo,
          void *userdata); /* Set this to THIS_MODULE. */
 struct module *me;
};該結構中有以下幾個參數:
struct list_head list:用來掛接到目標鏈表,必須初始化為{NULL, NULL};
name: 該目標名稱的名稱,必須是唯一的;
checkentry函數:用於對使用者層傳入的資料進行合法性檢查,如目標資料長度是否正確,是否是在正確的表中使用等;
destroy函數:用於釋放該目標中動態分配的資源,在規則刪除時會調用;
target
函數:該函數是最主要函數,完成對資料包的策略處理,包括對包中的資料進行修改,函數返回結果可能是NF_ACCEPT(接受)/NF_DROP(丟棄)
/NF_STOLEN(偷竊,指該包處理由該目標接管,不再由系統網路棧處理)/IPT_CONTINUE(繼續,繼續按後面的規則對該包進行檢查)等;
struct module *me:指向模組本身。 本例中的目標結構定義如下:
static struct ipt_target ipt_id_reg = {
 .name  = "ID",
 .target  = target,
 .checkentry = checkentry,
 .me  = THIS_MODULE,
}; target函數是目標的核心函數,返回1表示資料符合目標條件,0表示不目標,函數定義為:
 unsigned int (*target)(struct sk_buff **pskb,
          const struct net_device *in,
          const struct net_device *out,
          unsigned int hooknum,
          const void *targinfo,
          void *userdata);
在target函數參數為:
skb:資料包
in:資料進入的網卡
out:資料發出的網卡
hooknum:hook號,取值為:NF_IP_PRE_ROUTING/NF_IP_LOCAL_IN/NF_IP_FORWARD/NF_IP_LOCAL_OUT/NF_IP_POST_ROUTING之一
targetinfo:目標條件資訊的指標
offset:片段資料位移量
userdata:特殊專用資料,目前核心中基本沒有用到,通常都是NULL;  check函數對資料進行檢查,返回1表示資料合法,0表示非法,函數定義為:
 int (*checkentry)(const char *tablename,
     const struct ipt_entry *e,
     void *targinfo,
     unsigned int targinfosize,
     unsigned int hook_mask);tablename:表名,如“filter”,“nat”,“mangle”等,可用來限制目標只能在指定的表中處理;
struct ipt_entry *e:指向規則的指標;
targetinfo:使用者空間傳入的目標條件資訊的指標;
targetinfosize:使用者空間傳入目標條件資訊的長度;
hook_mask:表示掛接點(PREROUTING/INPUT/FORWARD/OUTPUT/POSTROUTING)的掩碼,可用來限制目標只能在指定的掛接點中處理; 宏IPT_ALIGN用來得到實際目標結構的實際大小。 本例中需要檢查目標資料長度是否正確,表名是否是“mangle”,因為需要對資料進行修改,所以一般要在mangle表中進行處理,其實也可以不限制; destroy函數釋放該目標中動態分配的資源,無傳回值,函數定義為:
 void (*destroy)(void *targetinfo, unsigned int targetinfosize);
函數參數同check()函數說明,在本例中,並沒有分配相關資源,所以沒定義此函數。 最後在模組初始化函數中要調用ipt_register_target()函數將一個ipt_target目標結構掛接到系統目標鏈表中,在模組的結束函數中調用ipt_unregister_target()函數將目標結構從目標鏈表中去除。 修改net/ipv4/netfilter/Makefile和Kconfig檔案,加入關於ipt_ID相關內容即可在編譯核心中自動編譯,或者單獨直接將其編譯為模組插入核心。 ipt_ID.c代碼如下:/* This is a module which is used for setting the ID field of a packet.
 * based on ipt_ID.c
 * in fact, it's useless.
 */#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <net/checksum.h>#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netfilter_ipv4/ipt_ID.h>MODULE_LICENSE("GPL");
MODULE_AUTHOR("yfydz<yfydz_no1@hotmail.com
>");
MODULE_DESCRIPTION("iptables ID mangling module"); static unsigned int
target(struct sk_buff **pskb,
       const struct net_device *in,
       const struct net_device *out,
       unsigned int hooknum,
       const void *targinfo,
       void *userinfo)
{
 const struct ipt_id_target_info *idinfo = targinfo;// 如果當前包的ID值和指定值不同,將指定值賦值給IP頭中的ID欄位
 if ((*pskb)->nh.iph->id != idinfo->id) {
  u_int16_t diffs[2];  if (!skb_ip_make_writable(pskb, sizeof(struct iphdr)))
   return NF_DROP;  diffs[0] = htons((*pskb)->nh.iph->id) ^ 0xFFFF;
  (*pskb)->nh.iph->id
   = idinfo->id;
  diffs[1] = htons((*pskb)->nh.iph->id);
// 由於IP包資料進行了修改,需要重新計算IP頭中的校正和
  (*pskb)->nh.iph->check
   = csum_fold(csum_partial((char *)diffs,
       sizeof(diffs),
       (*pskb)->nh.iph->check
       ^0xFFFF));
  (*pskb)->nfcache |= NFC_ALTERED;
 }
// 返回IPT_CONTINUE,表示繼續按下一條規則處理資料包
 return IPT_CONTINUE;
} static int
checkentry(const char *tablename,
    const struct ipt_entry *e,
           void *targinfo,
           unsigned int targinfosize,
           unsigned int hook_mask)
{
 const u_int8_t id = ((struct ipt_id_target_info *)targinfo)->id;
// 檢查使用者傳入資料的長度是否正確
 if (targinfosize != IPT_ALIGN(sizeof(struct ipt_id_target_info))) {
  printk(KERN_WARNING "ID: targinfosize %u != %Zu/n",
         targinfosize,
         IPT_ALIGN(sizeof(struct ipt_id_target_info)));
  return 0;
 }
// 判斷是否在mangle表中
 if (strcmp(tablename, "mangle") != 0) {
  printk(KERN_WARNING "ID: can only be called from /"mangle/" table, not /"%s/"/n", tablename);
  return 0;
 } return 1;
} static struct ipt_target ipt_id_reg = {
 .name  = "ID",
 .target  = target,
 .checkentry = checkentry,
 .me  = THIS_MODULE,
}; static int __init init(void)
{
 return ipt_register_target(&ipt_id_reg);
} static void __exit fini(void)
{
 ipt_unregister_target(&ipt_id_reg);
} module_init(init);
module_exit(fini); 如果對包中的資料進行了修改,需要修改資料頭相應的校正和,如IP頭校正和和TCP/UDP頭校正和,所以可能會需要調用如
csum_partial(),tcp_v4_check(),csum_tcpudp_magic(),ip_send_check(iph)等函數計
算校正和,這些函數的使用方法可參考核心中的代碼執行個體。 有些目標對包的操作比較複雜,如REJECT,MIRROR等,需要構造回應包發出,所以一般目標要比匹配要複雜一些。3. iptables使用者層目標模組iptables中的擴充目標模組是以動態庫方式處理,在命令列中用“-j
xxx”來使iptables調用相應的libipt_xxx.so動態庫,擴充的目標代碼通常在
iptables-<version>/extension目錄下,編譯好的動態庫預設放在/usr/local/lib/iptables
目錄下。 目標動態庫的作用用於解析使用者輸入的目標資訊,顯示目標資訊等功能。 寫好libipt_xxx.c程式後放到iptables-<version>/extension目錄下,修改該目錄下的Makefile檔案,將xxx添加到擴充表中,make就能自動將其編譯為動態庫。 對於目標,最重要的資料結構就是struct iptables_target結構,擴充的目標程式就是要定義一個這個結構並將其掛接到iptables目標鏈表中,該結構定義如下: struct iptables_target
{
 struct iptables_target *next; ipt_chainlabel name; const char *version; /* Size of target data. */
 size_t size; /* Size of target data relevent for userspace comparison purposes */
 size_t userspacesize; /* Function which prints out usage message. */
 void (*help)(void); /* Initialize the target. */
 void (*init)(struct ipt_entry_target *t, 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,
       struct ipt_entry_target **target); /* Final check; exit if not ok. */
 void (*final_check)(unsigned int flags); /* Prints out the target iff non-NULL: put space at end */
 void (*print)(const struct ipt_ip *ip,
        const struct ipt_entry_target *target, int numeric); /* Saves the targinfo in parsable form to stdout. */
 void (*save)(const struct ipt_ip *ip,
       const struct ipt_entry_target *target); /* Pointer to list of extra command-line options */
 struct option *extra_opts; /* Ignore these men behind the curtain: */
 unsigned int option_offset;
 struct ipt_entry_target *t;
 unsigned int tflags;
 unsigned int used;
#ifdef NO_SHARED_LIBS
 unsigned int loaded; /* simulate loading so options are merged properly */
#endif
}; struct iptables_target結構參數說明如下:
next: 目標鏈表的下一個,目標鏈表是一個單向鏈表;
name:目標的名稱,必須是唯一的;
version:iptables的版本;
size:目標結構的資料長度;
userspacesize:用於目標部分的資料長度,通常此值等於size,但某些情況可能會小於size;
help函數:列印協助資訊,當 "-j xxx -h"時調用;
init函數:初始化函數,可對目標結構賦初值;
parse函數:解析使用者輸入參數,這是最主要的處理函數;
final_check函數:對使用者資料進行最後的檢查;
print函數:列印目標資訊,iptables -L時調用
save函數:儲存當前iptables規則時列印目標格式,被iptables-save程式調用;
extra_opts:選項資訊,選項格式是標準的UNIX選項格式,通過getopt函數識別;
option_offset:選項位移;
t:指向iptables規則;
tflags:規則相關標誌
used: 模組使用計數; 本例中,使用者輸入的參數是新的ID值,格式為:
“-j ID --set-id id_value

id_value
為id的實際數值。 libipt_id.c函數就是填寫struct iptables_target結構,然後定義動態庫初始化函數_init()將該結構掛接到iptables的選項鏈表中。程式在libipt_id.c的基礎上修改,程式比較簡單,直接將代碼列出,相關說明在注釋中: /* libipt_id.c */
/* Shared library add-on to iptables to add ID target support. */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>#include <iptables.h>
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netfilter_ipv4/ipt_ID.h>struct idinfo {
 struct ipt_entry_target t;
 struct ipt_id_target_info id;
};/* Function which prints out usage message. */
static void
help(void)
{
 unsigned int i; printf(
"ID target v%s options:/n"
"  --set-id value                 Set ID of IP headere/n",
IPTABLES_VERSION);} // opts結構第一個參數為選項名稱,
// 第二個參數為1表示選項名稱後還帶參數,為0表示選項名稱後不帶參數
// 第3個參數是標誌,表示返回資料的格式,一般都設為0
// 第4個參數表示該選項的索引值
static struct option opts[] = {
 { "set-id", 1, 0, '1' },
 { 0 }
};/* Initialize the target. */
static void
init(struct ipt_entry_target *t, unsigned int *nfcache)
{
// 空函數,不需要預先處理
} static void
parse_id(const unsigned char *s, struct ipt_id_target_info *info)
{
 unsigned int i, id; if (string_to_number(s, 0, 255, &id) != -1) {
// 注意要將主機序資料轉換為網路序
      info->id = htons((u_int16_t )id);
      return;
 }
 exit_error(PARAMETER_PROBLEM, "Bad ID value `%s'", s);
} /* Function which parses command options; returns true if it
   ate an option */
static int
parse(int c, char **argv, int invert, unsigned int *flags,
      const struct ipt_entry *entry,
      struct ipt_entry_target **target)
{
 struct ipt_id_target_info *idinfo
  = (struct ipt_id_target_info *)(*target)->data; switch (c) {
 case '1':
  if (*flags)
   exit_error(PARAMETER_PROBLEM,
              "ID target: Cant specify --set-id twice");
  parse_id(optarg, idinfo);
  *flags = 1;
  break; default:
  return 0;
 } return 1;
} static void
final_check(unsigned int flags)
{
 if (!flags)
  exit_error(PARAMETER_PROBLEM,
             "ID target: Parameter --set-id is required");
} static void
print_id(u_int8_t id, int numeric)
{
 unsigned int i; printf("0x%x ", ntohs(id));
} /* Prints out the targinfo. */
static void
print(const struct ipt_ip *ip,
      const struct ipt_entry_target *target,
      int numeric)
{
 const struct ipt_id_target_info *idinfo =
  (const struct ipt_id_target_info *)target->data;
 printf("ID set ");
 print_id(idinfo->id, numeric);
} /* Saves the union ipt_targinfo in parsable form to stdout. */
static void
save(const struct ipt_ip *ip, const struct ipt_entry_target *target)
{
 const struct ipt_id_target_info *idinfo =
  (const struct ipt_id_target_info *)target->data; printf("--set-id 0x%x ", ntohs(idinfo->id));
} static
struct iptables_target id
= { NULL,
    "ID",
    IPTABLES_VERSION,
    IPT_ALIGN(sizeof(struct ipt_id_target_info)),
    IPT_ALIGN(sizeof(struct ipt_id_target_info)),
    &help,
    &init,
    &parse,
    &final_check,
    &print,
    &save,
    opts
}; void _init(void)
{
 register_target(&id);
} 4. 結論netfilter/iptables可以很方便地擴充新的目標模組,只需要按指定的方式編寫代碼就可以,讓開發人員將注意力集中在功能的具體實
現上,而不用再考慮其他因素,在具體實現時可以以現成的目標模組為基礎進行修改即可,甚至不需要更仔細瞭解內部結構的定義就可以完成編碼,是一個程式模組
化的很優秀的實現例子。在netfilter官方網站(www.netfilter.org
)上提供patch-o-matic程式包,其中包含了許多愛好者編寫的未併入Linux官方核心中的匹配和目標模組。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.