自定义iptables/netfilter的匹配模块

本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: [email protected]
来源: http://yfydz.cublog.cn

1. 前言
Linux中的netfilter提供了一个防火墙框架,具有很好的扩展性,除了自带的模块之外,用户可以根据自己的需求定义新的防火墙模块加入其中,而编程过程也不是很复杂,只要依葫芦画瓢即可,可在原来的类似功能的模块基础上修改即可,甚至对各函数是如何调用,一些内部结构是如何定义的都不用详细了解,本文即介绍如何编写自定义的匹配模块。
 
匹配(match)是防火墙规则策略中的条件部分,匹配结果返回0(匹配失败)或1(匹配成功),从而确定数据包是否符合策略。在具体实现时分为两部分,内核部分和用户空间部分:在内核中是以netfilter的扩展模块实现,定义该模块并将其挂接到netfilter的匹配链表中后,在内核进行匹配检测时自动根据匹配名称查找该模块而调用相应的匹配函数,完成真正的匹配功能;在用户空间,匹配模块是作为iptables的一个扩展动态库来实现,只是一个用户接口,不完成实际匹配功能,完成接收用户输入并将匹配数据结构传递到内核的功能,该库的名称有限制,必须为libipt_xxx.so,其中“xxx”是该匹配的名字,通
常匹配名称是用小写。

2. 内核模块
 
匹配在2.4内核和在2.6内核中的函数参数略有区别,两者不兼容,但只要简单修改后即可相互移植,主体部分不需要改变,本文以2.6内核的模块为例。
 
为方便说明,还是通过举例来进行说明,要进行匹配的对象是一个IP地址段,但该段中可能有一些IP不符合匹配条件,形成地址“洞”,这种情况是经常在实际应用中出现,对一个地址段中的几个IP地址要进行特别处理。
 
在内核中已经自带了net/ipv4/netfilter/ipt_iprange.c模块用来匹配地址段,我们可以以此模块为基础来进行修改,加入地址“洞”的处理。
 
首先要定义要进行匹配的数据结构,用来描述匹配条件,以ipt_iprange.h头文件为基础修改为:

/* include/linux/netfilter_ipv4/ipt_iprangehole.h */
#ifndef _IPT_IPRANGEHOLE_H
#define _IPT_IPRANGEHOLE_H
#define IPRANGE_SRC  0x01 /* Match source IP address */
#define IPRANGE_DST  0x02 /* Match destination IP address */
#define IPRANGE_SRC_INV  0x10 /* Negate the condition */
#define IPRANGE_DST_INV  0x20 /* Negate the condition */
#define MAX_HOLES_NUM  10      /* 定义一个匹配中最多的IP“洞”的个数 */
 
struct ipt_iprangehole {
 /* Inclusive: network order. */
 u_int32_t min_ip, max_ip;
 u_int32_t holes[MAX_HOLES_NUM];  /* IP洞地址 */
};
 
struct ipt_iprangehole_info
{
 struct ipt_iprangehole src;
 struct ipt_iprangehole dst;
 /* Flags from above */
 u_int8_t flags;
};
#endif /* _IPT_IPRANGEHOLE_H */

然后是定义内核模块处理,最主要的是定义一个ipt_match匹配结构,该结构在include/linux/netfilter_ipv4/ip_tables.h中定义:
 
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;
};
 
该结构中有以下几个参数:
struct list_head list:用来挂接到匹配链表,必须初始化为{NULL, NULL};
name: 该匹配名称的名称,必须是唯一的;
match函数:该函数是最主要函数,完成对数据包的匹配条件检查,返回1表示匹配成功,0表示失败;
checkentry函数:用于对用户层传入的数据进行合法性检查,如匹配数据长度是否正确,是否是在正确的表中使用等;
destroy函数:用于释放该匹配中动态分配的资源,在规则删除时会调用;
struct module *me:指向模块本身。
 
本例中的匹配结构定义如下:
static struct ipt_match iprangehole_match =
{
 .list = { NULL, NULL },
 .name = "iprangehole",
 .match = &match,
 .checkentry = &check,
 .destroy = NULL,
 .me = THIS_MODULE
};
 
match函数是匹配的核心函数,返回1表示数据符合匹配条件,0表示不匹配,函数定义为:
 int (*match)(const struct sk_buff *skb,
       const struct net_device *in,
       const struct net_device *out,
       const void *matchinfo,
       int offset,
       int *hotdrop);
在match函数参数为:
skb:数据包
in:数据进入的网卡
out:数据发出的网卡
matchinfo:匹配条件信息的指针
offset:碎片数据偏移量
hotdrop:将数据立即丢弃
 
该函数就是根据skb数据包中的内容,按matchinfo匹配条件进行匹配,检查数据包内容是否符合匹配条件,支持取反处理,是将基本匹配结果(1/0)与是否取反(1/0)进行异或而得到最终结果。在本例中,在ipt_iprange.c中增加对数据包地址是否属于IP“洞”的检查即可。
 
注意hotdrop参数,一般匹配是不决定数据的处理策略的,但该参数的使用可以使内核立即丢弃该数据包。
 
check函数对数据进行检查,返回1表示数据合法,0表示非法,函数定义为:
 int (*checkentry)(const char *tablename,
     const struct ipt_ip *ip,
     void *matchinfo,
     unsigned int matchinfosize,
     unsigned int hook_mask);
tablename:表名,如“filter”,“nat”,“mangle”等,可用来限制匹配只能在指定的表中处理;
struct ipt_ip *ip:包含相应IP头中的相关信息;
matchinfo:用户空间传入的匹配条件信息的指针;
matchinfosize:用户空间传入匹配条件信息的长度;
hook_mask:表示挂接点(PREROUTING/INPUT/FORWARD/OUTPUT/POSTROUTING)的掩码,可用来限制匹配只能在指定的挂接点中处理;
宏IPT_ALIGN用来得到实际匹配结构的实际大小。
本例中只需要检查匹配数据长度是否正确,处理和ipt_iprange相同,不用进行修改;
 
destroy函数释放该匹配中动态分配的资源,无返回值,函数定义为:
 void (*destroy)(void *matchinfo, unsigned int matchinfosize);
函数参数同check()函数说明,在本例中,并没有分配相关资源,该函数为空(NULL)。
 
最后在模块初始化函数中要调用ipt_register_match()函数将一个ipt_match匹配结构挂接到系统匹配链表中,在模块的结束函数中调用ipt_unregister_match()函数将匹配结构从匹配链表中去除。
 
修改net/ipv4/netfilter/Makefile和Kconfig文件,加入关于ipt_iprangehole相关内容即可在编译内核中自动编译,或者单独直接将其编译为模块插入内核。
 
ipt_iprangehole.c代码如下:
/*
 * net/ipv4/netfilter/ipt_iprangehole.c
 * iptables module to match IP address ranges with holes
 * based on ipt_iprange.c
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netfilter_ipv4/ipt_iprangehole.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yfydz <[email protected]>");
MODULE_DESCRIPTION("iptables arbitrary IP range with holes match module");
 
#if 0
#define DEBUGP printk
#else
#define DEBUGP(format, args...)
#endif
 
// 此为新增函数,检查地址是否属于IP洞
static int in_holes(u_int32_t addr, u_int32_t *holes)
{
 int i;
 for(i=0; i < MAX_HOLES_NUM; i++)
  if(addr == holes[i]) return 1;
 return 0;
}
 
static int
match(const struct sk_buff *skb,
      const struct net_device *in,
      const struct net_device *out,
      const void *matchinfo,
      int offset, int *hotdrop)
{
 const struct ipt_iprangehole_info *info = matchinfo;
 const struct iphdr *iph = skb->nh.iph;
 if (info->flags & IPRANGE_SRC) {
  if (((ntohl(iph->saddr) < ntohl(info->src.min_ip))
     || (ntohl(iph->saddr) > ntohl(info->src.max_ip))
     || (in_holes(iph->saddr, info->src.holes)))
    ^ !!(info->flags & IPRANGE_SRC_INV)) {
   DEBUGP("src IP %u.%u.%u.%u NOT in range %s"
          "%u.%u.%u.%u-%u.%u.%u.%u\n",
    NIPQUAD(iph->saddr),
           info->flags & IPRANGE_SRC_INV ? "(INV) " : "",
    NIPQUAD(info->src.min_ip),
    NIPQUAD(info->src.max_ip));
   return 0;
  }
 }
 if (info->flags & IPRANGE_DST) {
  if (((ntohl(iph->daddr) < ntohl(info->dst.min_ip))
     || (ntohl(iph->daddr) > ntohl(info->dst.max_ip))
     || (in_holes(iph->daddr, info->dst.holes)))
    ^ !!(info->flags & IPRANGE_DST_INV)) {
   DEBUGP("dst IP %u.%u.%u.%u NOT in range %s"
          "%u.%u.%u.%u-%u.%u.%u.%u\n",
    NIPQUAD(iph->daddr),
           info->flags & IPRANGE_DST_INV ? "(INV) " : "",
    NIPQUAD(info->dst.min_ip),
    NIPQUAD(info->dst.max_ip));
   return 0;
  }
 }
 return 1;
}
 
static int check(const char *tablename,
   const struct ipt_ip *ip,
   void *matchinfo,
   unsigned int matchsize,
   unsigned int hook_mask)
{
 /* verify size */
 if (matchsize != IPT_ALIGN(sizeof(struct ipt_iprangehole_info)))
  return 0;
 return 1;
}
 
static struct ipt_match iprangehole_match =
{
 .list = { NULL, NULL },
 .name = "iprangehole",
 .match = &match,
 .checkentry = &check,
 .destroy = NULL,
 .me = THIS_MODULE
};
 
static int __init init(void)
{
 return ipt_register_match(&iprangehole_match);
}
 
static void __exit fini(void)
{
 ipt_unregister_match(&iprangehole_match);
}
 
module_init(init);
module_exit(fini);

3. iptables用户层匹配模块
 
iptables中的扩展匹配模块是以动态库方式处理,在命令行中用“-m xxx”来使iptables调用相应的libipt_xxx.so动态库,扩展的匹配代码通常在 iptables-<version>/extension目录下,编译好的动态库缺省放在/usr/local/lib/iptables 目录下。
 
匹配动态库的作用用于解析用户输入的匹配信息,显示匹配信息等功能。
 
写好libipt_xxx.c程序后放到iptables-<version>/extension目录下,修改该目录下的Makefile文件,将xxx添加到扩展表中,make就能自动将其编译为动态库。
 
对于匹配,最重要的数据结构就是struct iptables_match结构,扩展的匹配程序就是要定义一个这个结构并将其挂接到iptables匹配链表中,该结构定义如下:
 
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;
 unsigned int used;
#ifdef NO_SHARED_LIBS
 unsigned int loaded; /* simulate loading so options are merged properly */
#endif
};
 
struct iptables_match结构参数说明如下:
next: 匹配链表的下一个,匹配链表是一个单向链表;
name:匹配的名称,必须是唯一的;
version:iptables的版本;
size:匹配结构的数据长度;
userspacesize:用于匹配部分的数据长度,通常此值等于size,但某些情况可能会小于size,如limit匹配,limit匹配结构中有一些变化的参数,在进行匹配是不需要对这些参数进行匹配的,只匹配固定的数据部分;
help函数:打印帮助信息,当 "-m xxx -h"时调用;
init函数:初始化函数,可对匹配结构赋初值;
parse函数:解析用户输入参数,这是最主要的处理函数;
final_check函数:对用户数据进行最后的检查;
print函数:打印匹配信息,iptables -L时调用
save函数:保存当前iptables规则时打印匹配格式,被iptables-save程序调用;
extra_opts:选项信息,选项格式是标准的UNIX选项格式,通过getopt函数识别;
option_offset:选项偏移;
m:指向iptables规则;
used: 模块使用计数;
 
本例中,要求用户输入IP地址段及相应的IP“洞”,规定数据格式为:
  ip-ip/ip[,ip...]
ip是点分数据格式表示的IP地址,如1.1.1.1;
"-"连接IP起始值和终止值;
"/"分隔地址段和地址“洞”
地址“洞”部分的IP地址用“,”分隔,中间没有空格。
 
例如,地址范围为10.10.10.1到10.10.10.100,地址“洞”为10.10.10.10, 10.10.10.20, 10.10.10.30,表示格式如下:
10.10.10.1-10.10.10.100/10.10.10.10,10.10.10.20,10.10.10.30
 
libipt_iprangehole.c函数就是填写struct iptables_match iprangehole结构,然后定义动态库初始化函数_init()将该结构挂接到iptables的选项链表中。程序在 libipt_iprange.c的基础上修改,程序比较简单,直接将代码列出,相关说明在注释中:

/* libipt_iprangehole.c */
/* Shared library add-on to iptables to add IP range matching support. */
#include <stdio.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include <iptables.h>
#include <linux/netfilter_ipv4/ipt_iprangehole.h>
/* Function which prints out usage message. */
static void
help(void)
{
 printf(
"iprange match v%s options:\n"
"[!] --src-range ip-ip/ip_holes        Match source IP in the specified range\n"
"[!] --dst-range ip-ip/ip_holes        Match destination IP in the specified range\n"
"\n",
IPTABLES_VERSION);
}
 
// opts结构第一个参数为选项名称,
// 第二个参数为1表示选项名称后还带参数,为0表示选项名称后不带参数
// 第3个参数是标志,表示返回数据的格式,一般都设为0
// 第4个参数表示该选项的索引值
static struct option opts[] = {
 { "src-range", 1, 0, '1' },
 { "dst-range", 1, 0, '2' },
 {0}
};
 
/* Initialize the match. */
static void
init(struct ipt_entry_match *m, unsigned int *nfcache)
{
 struct ipt_iprangehole_info *info = (struct ipt_iprangehole_info *)m->data;
 /* Can't cache this. */
 *nfcache |= NFC_UNKNOWN;
// 将结构数据初始化为0
 bzero(info, sizeof(struct ipt_iprangehole_info));
}
 
static void
parse_iprange(char *arg, struct ipt_iprangehole *range)
{
 char *dash, *slash;
 struct in_addr *ip;
 slash = strchr(arg, '/');
 if(slash)
  *slash = '\0';
 dash = strchr(arg, '-');
 if (dash)
  *dash = '\0';
//解析地址段部分  
 ip = dotted_to_addr(arg);
 if (!ip)
  exit_error(PARAMETER_PROBLEM, "iprange match: Bad IP address `%s'\n",
      arg);
 range->min_ip = ip->s_addr;
 if (dash) {
  ip = dotted_to_addr(dash+1);
  if (!ip)
   exit_error(PARAMETER_PROBLEM, "iprange match: Bad IP address
`%s'\n",
       dash+1);
  range->max_ip = ip->s_addr;
 } else
  range->max_ip = range->min_ip;
// 解析地址洞部分 
 if( (range->max_ip != range->min_ip) && slash ){
  char *ptr;
  int i=0;
  do{
   ptr = strchr(slash+1, ',');
   if(ptr)
    ptr = '\0';
   ip = dotted_to_addr(slash+1);
   if (!ip)
    exit_error(PARAMETER_PROBLEM, "iprange match: Bad IP
address `%s'\n",
       slash+1);
   if(ip->s_addr){
    range->holes[i] = ip->s_addr;
    i++;
   }
   slash = ptr;
  }while(slash != NULL);
 }
}
 
/* 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,
      unsigned int *nfcache,
      struct ipt_entry_match **match)
{
 struct ipt_iprangehole_info *info = (struct ipt_iprangehole_info *)(*match)->data;
 switch (c) {
 case '1':
  if (*flags & IPRANGE_SRC)
   exit_error(PARAMETER_PROBLEM,
       "iprange match: Only use --src-range ONCE!");
  *flags |= IPRANGE_SRC;
  info->flags |= IPRANGE_SRC;
// 检查是否带“!”要对匹配进行取反处理
  check_inverse(optarg, &invert, &optind, 0);
  if (invert) {
   info->flags |= IPRANGE_SRC_INV;
   printf("hoho\n");
  }
  parse_iprange(optarg, &info->src);  
  break;
 case '2':
  if (*flags & IPRANGE_DST)
   exit_error(PARAMETER_PROBLEM,
       "iprange match: Only use --dst-range ONCE!");
  *flags |= IPRANGE_DST;
  info->flags |= IPRANGE_DST;
  check_inverse(optarg, &invert, &optind, 0);
  if (invert)
   info->flags |= IPRANGE_DST_INV;
  parse_iprange(optarg, &info->dst);  
  *flags = 1;
  break;
 default:
  return 0;
 }
 return 1;
}
/* Final check; must have specified --src-range or --dst-range. */
static void
final_check(unsigned int flags)
{
 if (!flags)
  exit_error(PARAMETER_PROBLEM,
      "iprange match: You must specify `--src-range' or `--dst-
range'");
}
 
static void
print_iprange(const struct ipt_iprangehole *range)
{
 const unsigned char *byte_min, *byte_max;
 int i;
 byte_min = (const unsigned char *) &(range->min_ip);
 byte_max = (const unsigned char *) &(range->max_ip);
 printf("%d.%d.%d.%d-%d.%d.%d.%d",
  byte_min[0], byte_min[1], byte_min[2], byte_min[3],
  byte_max[0], byte_max[1], byte_max[2], byte_max[3]);
 if(range->holes[0]){
  for(i=0; i<MAX_HOLES_NUM; i++)
   if(! range->holes[i]) break;
  byte_min = (const unsigned char *) &(range->holes[i]);
  printf("%c%d.%d.%d.%d", i == 0 ?  '/' : ',' ,
   byte_min[0], byte_min[1], byte_min[2], byte_min[3]);
 }
 else
  printf(" ");
}

/* Prints out the info. */
static void
print(const struct ipt_ip *ip,
      const struct ipt_entry_match *match,
      int numeric)
{
 struct ipt_iprangehole_info *info = (struct ipt_iprangehole_info *)match-
>data;
 if (info->flags & IPRANGE_SRC) {
  printf("source IP range ");
  if (info->flags & IPRANGE_SRC_INV)
   printf("! ");
  print_iprange(&info->src);
 }
 if (info->flags & IPRANGE_DST) {
  printf("destination IP range ");
  if (info->flags & IPRANGE_DST_INV)
   printf("! ");
  print_iprange(&info->dst);
 }
}
 
/* Saves the union ipt_info in parsable form to stdout. */
static void
save(const struct ipt_ip *ip, const struct ipt_entry_match *match)
{
 struct ipt_iprangehole_info *info = (struct ipt_iprangehole_info *)match-
>data;
 if (info->flags & IPRANGE_SRC) {
  if (info->flags & IPRANGE_SRC_INV)
   printf("! ");
  printf("--src-range ");
  print_iprange(&info->src);
  if (info->flags & IPRANGE_DST)
   fputc(' ', stdout);
 }
 if (info->flags & IPRANGE_DST) {
  if (info->flags & IPRANGE_DST_INV)
   printf("! ");
  printf("--dst-range ");
  print_iprange(&info->dst);
 }
}
 
static
struct iptables_match iprangehole
= { NULL,
    "iprangehole",
    IPTABLES_VERSION,
    IPT_ALIGN(sizeof(struct ipt_iprangehole_info)),
    IPT_ALIGN(sizeof(struct ipt_iprangehole_info)),
    &help,
    &init,
    &parse,
    &final_check,
    &print,
    &save,
    opts
};
void _init(void)
{
 register_match(&iprangehole);
}

4. 结论
netfilter/iptables可以很方便地扩展新的匹配模块,只需要按指定的方式编写代码就可以,让开发者将注意力集中在功能的具体实现上,而不用再考虑其他因素,在具体实现时可以以现成的匹配模块为基础进行修改即可,甚至不需要更仔细了解内部结构的定义就可以完成编码,是一个程序模块化的很优秀的实现例子。在netfilter官方网站(www.netfilter.org)上提供patch-o-matic程序包,其中包含了许多爱好者编写的未并入Linux官方内核中的匹配和目标模块。

猜你喜欢

转载自cxw06023273.iteye.com/blog/866851