上一篇文章分析了iptables代码下发运作的流程细节,篇幅有限还有很多需要补充.关于netfilter的框架网上已经被讲烂了,框架很简单,但是实现却不简单.但不论什么都要最终归到实际应用上,才能体现其价值.
下面我们就以iptables1.4.21 ubuntu14 32位 内核版本 3.13.0为环境来开发一个最常规的扩展应用.
首先下载iptables源码: (这个网站有很多东西,可以好好看看)
关于linux kernel netfilter开启的配置,这里不多说,默认内核已经配置好了.(当然对于ubuntu14 系统默认把netfilter大部分都编译成了内核模块,需要什么自己手动安装即可)
先说说编译iptables,很简单 看看里面的说明文档即可,例INSTALL:
$./configure
$make
$make install
./configure 还可以支持一些选项参数 ,里面都有说明。例子如下:
./configure --prefix=/home/iptables --host=arm-linux // host可以不指定,自动判定,默认就是x86
这里还要补充点知识:动作的说明:
iptables动作---------DROP、ACCEPT、REJECT
◆ACCEPT
一旦包满足了指定的匹配条件,就会被ACCEPT,并且不会再去匹配当前链中的其他规则或同一个表内的其他规则,但它还要通过其他表中的链
◆DROP
如果包符合条件,这个target就会把它丢掉,也就是说包的生命到此结束,不会再向前走一步,效果就是包被阻塞了。在某些情况下,这个target会引起意外的结果,因为它不会向发送者返回任何信息,也不会向路由器返回信息,这就可能会使连接的另一方的sockets因苦等回音而亡:)
解决这个问题的较好的办法是使用REJECT target,(注:因为它在丢弃包的同时还会向发送者返回一个错误信息,这样另一方就能正常结束),尤其是在阻止端口扫描工具获得更多的信息时,可以隐蔽被过滤掉的端口等等(译者注:因为扫描工具扫描一个端口时,如果没有返回信息,一般会认为端口未打开或被防火墙等设备过滤掉了)。还要注意如果包在子链中被DROP了,那么它在主链里也不会再继续前进,不管是在当前的表还是在其他表里。
◆REJECT
REJECT和DROP基本一样,区别在于它除了阻塞包之外,还向发送者返回错误信息。现在,此target还只能用在INPUT、FORWARD、OUTPUT和它们的子链里,而且包含 REJECT的链也只能被它们调用,否则不能发挥作用。它只有一个选项,是用来控制返回的错误信息的种类的。
还有其他的动作:
LOG 用来记录与数据包相关的信息
MARK 设置mark值,这个值是一个无符号的整数
MASQUERADE 和SNAT的作用相同,区别在于它不需要指定--to-source
SNAT 源网络地址转换
DNAT 目的网络地址转换
REDIRECT 转发数据包一另一个端口
REJECT REJECT和DROP都会将数据包丢弃,区别在于REJECT除了丢弃数据包外,还向发送者返回错误信息
RETURN 使数据包返回上一层
TOS 用来设置IP头部中的Type Of Service字段
TTL 用于修改IP头部中Time To Live字段的值
ULOG ULOG可以在用户空间记录被匹配的包的信息,这些信息和整个包都会通过netlink socket被多播
QUEUE 为用户空间的程序或应用软件管理包队列
MIRROR 颠倒IP头部中的源目地址,然后再转发包
先一个实际的命令应用:
IPT -A INPUT -m pkttype –pkt-type broadcast -j REJECT
这里我们自己注册一个match 传递自己的参数并解析处理。
功能是禁止大于特定长度的ip报文通过 size由我们指定。
需要两个部分的工作
1.用户空间match的注册
2.内核空间match的注册
根据代码里pkttype的实际例子作为参考,很快我们就能干一票了.
需要修改的文件:
用户空间
Xt_pktsize.c // 路径extensions下
Xt_pktsize.h // 路径 include/linux/netfilter/
内核:这里编译为模块的方式
Xt_pktsize.c
Xt_pktsize.h
注册match当然首先要初始化它各个节点的元素
用户空间代码如下:
xt_pktsize.h
-
#ifndef _XT_PKTSIZE_H
-
#define _XT_PKTSIZE_H
-
-
struct xt_pktsize_info {
-
int pktsize;
-
int invert;
-
};
-
#endif /*_XT_PKTSIZE_H*/
xt_pktsize.c
-
/*
-
* Shared library add-on to iptables to match
-
* packets by their size
-
*
-
*/
-
#include <stdio.h>
-
#include <string.h>
-
#include <xtables.h>
-
#include <linux/if_packet.h>
-
#include <linux/netfilter/xt_pktsize.h>
-
-
enum {
-
O_pktsize = 0,
-
};
-
-
struct pktsizes {
-
const char *name;
-
unsigned char pktsize;
-
unsigned char printhelp;
-
const char *help;
-
};
-
-
-
-
static void print_types(void)
-
{
-
unsigned int i;
-
-
printf("Valid packet sizes:64-65535\n");
-
-
}
-
-
static void pktsize_help(void)
-
{
-
printf(
-
"pktsize match options:\n"
-
"[!] --pkt-size packetsize match packet size\n");
-
print_types();
-
}
-
-
static const struct xt_option_entry pktsize_opts[] = {
-
{.name = "pkt-size", .id = O_pktsize, .type = XTTYPE_STRING,
-
.flags = XTOPT_MAND | XTOPT_INVERT},
-
XTOPT_TABLEEND,
-
};
-
-
static void parse_pktsize(const char *pktsize, struct xt_pktsize_info *info)
-
{
-
unsigned int i,size;
-
char *buffer;
-
-
printf("pktsize is %s...\n",pktsize);
-
buffer = strdup(pktsize);
-
if(!xtables_strtoui(buffer, NULL, &size, 0, UINT16_MAX))
-
xtables_error(PARAMETER_PROBLEM, "Bad packet size '%s'", pktsize);
-
-
info->pktsize=size;
-
free(buffer);
-
-
}
-
-
static void pktsize_parse(struct xt_option_call *cb)
-
{
-
struct xt_pktsize_info *info = cb->data;
-
-
xtables_option_parse(cb);
-
parse_pktsize(cb->arg, info);
-
if (cb->invert)
-
info->invert = 1;
-
}
-
-
static void print_pktsize(const struct xt_pktsize_info *info)
-
{
-
unsigned int i;
-
-
-
-
printf("%d", info->pktsize); /* in case we didn't find an entry in named-packtes */
-
}
-
-
static void pktsize_print(const void *ip, const struct xt_entry_match *match,
-
int numeric)
-
{
-
const struct xt_pktsize_info *info = (const void *)match->data;
-
-
printf(" pktsize %s= ", info->invert ? "!" : "");
-
print_pktsize(info);
-
}
-
-
static void pktsize_save(const void *ip, const struct xt_entry_match *match)
-
{
-
const struct xt_pktsize_info *info = (const void *)match->data;
-
-
printf("%s --pkt-type ", info->invert ? " !" : "");
-
print_pktsize(info);
-
}
-
-
static struct xtables_match pktsize_match = {
-
.family = NFPROTO_UNSPEC,
-
.name = "pktsize",
-
.version = XTABLES_VERSION,
-
.size = XT_ALIGN(sizeof(struct xt_pktsize_info)),
-
.userspacesize = XT_ALIGN(sizeof(struct xt_pktsize_info)),
-
.help = pktsize_help,
-
.print = pktsize_print,
-
.save = pktsize_save,
-
.x6_parse = pktsize_parse,
-
.x6_options = pktsize_opts,
-
};
-
-
void _init(void)
-
{
-
xtables_register_match(&pktsize_match);
-
}
通过代码我们看到主要工作就是初始化struct xtables_match,然后注册而已. 它的核心函数是x6_parse/parse
-
/* Function which parses command options; returns true if it
-
ate an option */
-
/* entry is struct ipt_entry for example */
-
int (*parse)(int c, char **argv, int invert, unsigned int *flags,
-
const void *entry,
-
struct xt_entry_match **match);
或者
-
/* New parser */
-
void (*x6_parse)(struct xt_option_call *);
例子中为 pktsize_parse函数,之前我们已经分析过如何调用到某一个match的parse函数.主要解析ipt_entry_match里data(其实就是解析命令参数赋值给它)
除了parse函数还有options,即解析--XXX的参数需要用到的东西,也需要我们去填写。
下面看看内核部分:
xt_pktsize.h
-
#ifndef _XT_PKTSIZE_H
-
#define _XT_PKTSIZE_H
-
-
struct xt_pktsize_info {
-
int pktsize;
-
int invert;
-
};
-
#endif /*_XT_PKTSIZE_H*/
xt_pktsize.c
-
#include <linux/module.h>
-
#include <linux/skbuff.h>
-
#include <linux/if_ether.h>
-
#include <linux/if_packet.h>
-
#include <linux/in.h>
-
#include <linux/ip.h>
-
#include <linux/ipv6.h>
-
-
//#include <linux/netfilter/xt_pktsize.h>
-
#include "xt_pktsize.h"
-
#include <linux/netfilter/x_tables.h>
-
-
MODULE_LICENSE("GPL");
-
MODULE_AUTHOR("jack chen");
-
MODULE_DESCRIPTION("Xtables: link layer packet size match");
-
MODULE_ALIAS("ipt_pktsize");
-
MODULE_ALIAS("ip6t_pktsize");
-
-
static bool
-
pktsize_mt(const struct sk_buff *skb, struct xt_action_param *par)
-
{
-
const struct xt_pktsize_info *info = par->matchinfo;
-
u_int32_t size;
-
const struct iphdr *iph = ip_hdr(skb);
-
size=ntohs(iph->tot_len) - (iph->ihl*4);
-
printk("ip data pktsize is %d......,%d..,proto :%x\n",size,info->pktsize,iph->protocol);
-
-
return (info->pktsize < size) ;
-
//return 1;
-
}
-
-
static struct xt_match pktsize_mt_reg __read_mostly = {
-
.name = "pktsize",
-
.revision = 0,
-
.family = NFPROTO_UNSPEC,
-
.match = pktsize_mt,
-
.matchsize = sizeof(struct xt_pktsize_info),
-
.me = THIS_MODULE,
-
};
-
-
static int __init pktsize_mt_init(void)
-
{
-
return xt_register_match(&pktsize_mt_reg);
-
}
-
-
static void __exit pktsize_mt_exit(void)
-
{
-
xt_unregister_match(&pktsize_mt_reg);
-
}
-
-
module_init(pktsize_mt_init);
-
module_exit(pktsize_mt_exit);
这里我们计算ip报文的有效载荷的长度即ip包的总长度-ip头的长度
size=ntohs(iph->tot_len) - (iph->ihl*4);
同样或许我们还需要判断协议类型 protocal字等,简单说几个常用:
Decimal Keyword Protocol References
------- --------------- --------------------------------------- ------------------
1 ICMP Internet Control Message [RFC792]
6 TCP Transmission Control [RFC793]
17 UDP User Datagram [RFC768][JBP]
...
ip报文格式如下:
版本:占4位(bit),指IP协议的版本号。目前的主要版本为IPV4,即第4版本号,也有一些教育网和科研机构在使用IPV6。在进行通信时,通信双方的IP协议版本号必须一致,否则无法直接通信。
首部长度:占4位(bit),指IP报文头的长度。最大的长度(即4个bit都为1时)为15个长度单位,每个长度单位为4字节(TCP/IP标准,DoubleWord),所以IP协议报文头的最大长度为60个字节,最短为上图所示的20个字节。
服务类型:占8位(bit),用来获得更好的服务。其中的前3位表示报文的优先级,后面的几位分别表示要求更低时延、更高的吞吐量、更高的可靠性、更低的路由代价等。对应位为1即有相应要求,为0则不要求。
总长度:16位(bit),指报文的总长度。注意这里的单位为字节,而不是4字节,所以一个IP报文的的最大长度为65535个字节。
标识(identification):该字段标记当前分片为第几个分片,在数据报重组时很有用。
标志(flag):该字段用于标记该报文是否为分片(有一些可能不需要分片,或不希望分片),后面是否还有分片(是否是最后一个分片)。
片偏移:指当前分片在原数据报(分片前的数据报)中相对于用户数据字段的偏移量,即在原数据报中的相对位置。
生存时间:TTL(Time to Live)。该字段表明当前报文还能生存多久。每经过1ms或者一个网关,TTL的值自动减1,当生存时间为0时,报文将被认为目的主机不可到达而丢弃。使用过Ping命令的用户应该有印象,在windows中输入ping命令,在返回的结果中即有TTL的数值。
协议:该字段指出在上层(网络7层结构或TCP/IP的传输层)使用的协议,可能的协议有UDP、TCP、ICMP、IGMP、IGP等。
首部校验和:用于检验IP报文头部在传播的过程中是否出错,主要校验报文头中是否有某一个或几个bit被污染或修改了。
源IP地址:32位(bit),4个字节,每一个字节为0~255之间的整数,及我们日常见到的IP地址格式。
目的IP地址:32位(bit),4个字节,每一个字节为0~255之间的整数,及我们日常见到的IP地址格式。
当然对ip理解的越深刻越好了,那么这样就完成了一个简单的match扩展的例子,仅仅作为抛砖引玉,一个小小的开始.
阅读(5030) | 评论(0) | 转发(2) |