- filter表的实现
static struct nf_hook_ops ipt_ops[]
= { { { NULL, NULL }, ipt_hook, PF_INET, NF_IP_LOCAL_IN, NF_IP_PRI_FILTER },
{ { NULL, NULL }, ipt_hook, PF_INET, NF_IP_FORWARD, NF_IP_PRI_FILTER },
{ { NULL, NULL }, ipt_local_out_hook, PF_INET, NF_IP_LOCAL_OUT,
NF_IP_PRI_FILTER }
};
-
ipt_hook,定义于net/ipv4/netfilter/iptable_filter.c,Line89:
static unsigned int
ipt_hook(unsigned int hook,
struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
return ipt_do_table(pskb, hook, in, out, &packet_filter, NULL);
}
-
初始化各种变量,如IP头、数据区、输入输出设备、段偏移、规则入口及偏移量等等;
-
进行规则的匹配,首先调用ip_packet_match()函数(位于net/ipv4/netfilter/ip_tables.c,Line121)确定IP数据报是否匹配规则,若不匹配则跳到下一条规则(这个函数的主要工作大致为:依次处理源/目的IP地址、输入输出接口,然后对基本的规则进行匹配);
-
如果数据报匹配,则下面开始继续匹配matches和target,首先利用宏IPT_MATCH_ITERATE调用do_match()函数(下面单独分析)对扩展的match进行匹配,若不匹配则跳到下一条规则;
-
扩展match匹配后,首先调用ipt_get_target()获得target的偏移地址,然后对target进行匹配,这个匹配的过程要比match的匹配过程复杂一些,同样在下面单独分析。
if (!m->u.kernel.match->match(skb, in, out, m->data, offset, hdr, datalen, hotdrop))
return 1;
else
return 0;
m->u.kernel.match = match;
if (!t->u.kernel.target->target) {……}
static struct ipt_target ipt_standard_target
= { { NULL, NULL }, IPT_STANDARD_TARGET, NULL, NULL, NULL };
-
ipt_local_out_hook,定义于net/ipv4/netfilter/iptable_filter.c,Line99其功能与ipt_hook()相似,只不过为了防止DOS攻击而增加了对ratelimit的检查。
六、连接跟踪模块(Conntrack)
1. 概述
-
NF_IP_PRE_ROUTING
-
NF_IP_LOCAL_OUT
-
同时当使用NAT时,Conntrack也会有基于NF_IP_LOCAL_IN和NF_IP_POST_ROUTING的,只不过优先级很小。
2. 连接状态的管理
-
多元组
struct ip_conntrack_tuple
{
struct ip_conntrack_manip src;
struct {
u_int32_t ip;
union {
u_int16_t all;
struct { u_int16_t port; } tcp;
struct { u_int16_t port; } udp;
struct { u_int8_t type, code; } icmp;
} u;
u_int16_t protonum;
} dst;
};
-
连接记录
-
`struct nf_conntrack ct_general;`:nf_conntrack结构定义于include/linux/skbuff.h,Line89,其中包括一个计数器use和一个destroy函数。计数器use对本连接记录的公开引用次数进行计数
-
`struct ip_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX];`:其中的IP_CT_DIR_MAX是一个枚举类型ip_conntrack_dir(位于include/linux/netfilter_ipv4/ip_conntrack_tuple.h,Line65)的第3个成员,从这个结构实例在源码中的使用看来,实际上这是定义了两个tuple多元组的hash表项tuplehash[IP_CT_DIR_ORIGINAL/0]和tuplehash[IP_CT_DIR_REPLY/1],利用两个不同方向的tuple定位一个连接,同时也可以方便地对ORIGINAL以及REPLY两个方向进行追溯
-
`unsigned long status;`:这是一个位图,是一个状态域。在实际的使用中,它通常与一个枚举类型ip_conntrack_status(位于include/linux/netfilter_ipv4/ip_conntrack.h,Line33)进行位运算来判断连接的状态。其中主要的状态包括:
-
IPS_EXPECTED(_BIT),表示一个预期的连接
-
IPS_SEEN_REPLY(_BIT),表示一个双向的连接
-
IPS_ASSURED(_BIT),表示这个连接即使发生超时也不能提早被删除
-
IPS_CONFIRMED(_BIT),表示这个连接已经被确认(初始包已经发出)
-
-
`struct timer_list timeout;`:其类型timer_list位于include/linux/timer.h,Line11,其核心是一个处理函数。这个成员表示当发生连接超时时,将调用此处理函数
-
`struct list_head sibling_list;`:所谓“预期的连接”的链表,其中存放的是我们所期望的其它相关连接
-
`unsigned int expecting;`:目前的预期连接数量
-
`struct ip_conntrack_expect *master;`:结构ip_conntrack_expect位于include/linux/netfilter_ipv4/ip_conntrack.h,Line119,这个结构用于将一个预期的连接分配给现有的连接,也就是说本连接是这个master的一个预期连接
-
`struct ip_conntrack_helper *helper;`:helper模块。这个结构定义于include/linux/netfilter_ipv4/ip_conntrack_helper.h,Line11,这个模块提供了一个可以用于扩展Conntrack功能的接口。经过连接跟踪HOOK的每个数据报都将被发给每个已经注册的helper模块(注册以及卸载函数分别为ip_conntrack_helper_register()以及ip_conntrack_helper_unregister(),分别位于net/ipv4/netfilter/ip_conntrack_core.c,Line1136、1159)。这样我们就可以进行一些动态的连接管理了
-
`struct nf_ct_info infos[IP_CT_NUMBER];`:一系列的nf_ct_info类型(定义于include/linux/skbuff.h ,Line92,实际上就是nf_conntrack结构)的结构,每个结构对应于某种状态的连接。这一系列的结构会被sk_buff结构的nfct指针所引用,描述了所有与此连接有关系的数据报。其状态由枚举类型ip_conntrack_info定义(位于include/linux/netfilter_ipv4/ip_conntrack.h,Line12),共有5个成员:
-
IP_CT_ESTABLISHED:数据报属于已经完全建立的连接
-
IP_CT_RELATED: 数据报属于一个新的连接,但此连接与一个现有连接相关(预期连接);或者是ICMP错误
-
IP_CT_NEW:数据报属于一个新的连接
-
IP_CT_IS_REPLY:数据报属于一个连接的回复
-
IP_CT_NUMBER:不同IP_CT类型的数量,这里为7,NEW仅存于一个方向上
-
-
为NAT模块设置的信息(在条件编译中)
-
hash表
struct ip_conntrack_tuple_hash
{
struct list_head list;
struct ip_conntrack_tuple tuple;
struct ip_conntrack *ctrack;
};
3. 连接跟踪的实现
- 所谓“预期链接”
- ip_conntrack的状态转换
- 在NF_IP_PRE_ROUTING上对分片IP数据报的处理
4. 协议的扩展
struct ip_conntrack_protocol
{
struct list_head list;
u_int8_t proto; //协议号
const char *name;
int (*pkt_to_tuple)(const void *datah, size_t datalen,
struct ip_conntrack_tuple *tuple);
int (*invert_tuple)(struct ip_conntrack_tuple *inverse,
const struct ip_conntrack_tuple *orig);
unsigned int (*print_tuple)(char *buffer,
const struct ip_conntrack_tuple *);
unsigned int (*print_conntrack)(char *buffer,
const struct ip_conntrack *);
int (*packet)(struct ip_conntrack *conntrack,
struct iphdr *iph, size_t len,
enum ip_conntrack_info ctinfo);
int (*new)(struct ip_conntrack *conntrack, struct iphdr *iph,
size_t len);
void (*destroy)(struct ip_conntrack *conntrack);
int (*exp_matches_pkt)(struct ip_conntrack_expect *exp,
struct sk_buff **pskb);
struct module *me;
};
-
`int (*pkt_to_tuple)(……)`:其指向函数的作用是将协议加入到ip_conntrack_tuple的dst子结构中
-
`int (*invert_tuple)(……)`:其指向函数的作用是将源和目的多元组中协议部分的值进行互换,包括IP地址、端口等
-
`unsigned int (*print_tuple)(……)`:其指向函数的作用是打印多元组中的协议信息
-
`unsigned int (*print_conntrack)(……)`:其指向函数的作用是打印整个连接记录
-
`int (*packet)(……)`:其指向函数的作用是返回数据报的verdict值
-
`int (*new)(……)`:当此协议的一个新连接发生时,调用其指向的这个函数,调用返回true时再继续调用packet()函数
-
`int (*exp_matches_pkt)(……)`:其指向函数的作用是判断是否有数据报匹配预期的连接
七、网络地址转换模块(Network Address Translation)
1. 概述
-
NF_IP_PRE_ROUTING:可以在这里定义DNAT的规则,因为路由器进行路由时只检查数据报的目的IP地址,所以为了使数据报得以正确路由,我们必须在路由之前就进行DNAT
-
NF_IP_POST_ROUTING:可以在这里定义SNAT的规则,系统在决定了数据报的路由以后在执行该HOOK上的规则
-
NF_IP_LOCAL_OUT:定义对本地产生的数据报的DNAT规则
-
CONFIG_IP_NF_NAT_LOCAL定义后,NF_IP_LOCAL_IN上也可以定义DNAT规则。
2. 基于连接跟踪的相关数据结构
#ifdef CONFIG_IP_NF_NAT_NEEDED
struct {
struct ip_nat_info info;
union ip_conntrack_nat_help help;
#if defined(CONFIG_IP_NF_TARGET_MASQUERADE) || \
defined(CONFIG_IP_NF_TARGET_MASQUERADE_MODULE)
int masq_index;
#endif
} nat;
#endif
这是一个叫做nat的子结构,其中有3个成员:
-
一个ip_nat_info结构,这个结构下面会具体分析
-
一个ip_conntrack_nat_help结构,是一个空结构,为扩展功能而设
-
一个为伪装功能而设的index,从源码中对这个变量的使用看来,是对应所伪装网络接口的ID,也就是net_device中的ifindex成员
struct ip_nat_info
{
int initialized;
unsigned int num_manips;
struct ip_nat_info_manip manips[IP_NAT_MAX_MANIPS];
const struct ip_nat_mapping_type *mtype;
struct ip_nat_hash bysource, byipsproto;
struct ip_nat_helper *helper;
struct ip_nat_seq seq[IP_CT_DIR_MAX];
};
-
`int initialized;`:这是一个位图,表明源地址以及目的地址的地址绑定是否已被初始化
-
`unsigned int num_manips;`:这个成员指定了存放在下面的manip数组中的可执行操作的编号,在不同的HOOK以及不同的方向上,可执行操作是分别进行计数的。
-
`struct ip_nat_info_manip manips[IP_NAT_MAX_MANIPS];`:ip_nat_info_manip结构定义于include/linux/netfilter_ipv4/ip_nat.h,Line66,一个ip_nat_info_manip结构对应着一个可执行操作或地址绑定,其成员包括方向(ORIGINAL以及REPLY)、HOOK号、操作类型(由一个枚举类型ip_nat_manip_type定义,有IP_NAT_MANIP_SRC和IP_NAT_MANIP_DST两种)以及一个ip_conntrack_manip结构
-
`const struct ip_nat_mapping_type *mtype;`:ip_nat_mapping_type这个结构在整个内核源码中都没有定义,根据注释来看应该也是一个预留的扩展,一般就是NULL
-
`struct ip_nat_hash bysource, byipsproto;`:ip_nat_hash结构定义于include/linux/netfilter_ipv4/ip_nat.h,Line89,实际上就是一个带表头的ip_conntrack结构,跟连接跟踪中hash表的实现类似。其中,
-
bysource表是用来管理现有连接的
-
byipsproto表则管理已经完成的转换映射,以保证同一个IP不会同时有两个映射,避免地址转换冲突
-
-
`struct ip_nat_helper *helper;`:扩展用
-
`struct ip_nat_seq seq[IP_CT_DIR_MAX];`:这是为每一个方向(其实就两个方向)记录一个序列号。ip_nat_seq结构定义于include/linux/netfilter_ipv4/ip_nat.h,Line33,这个结构用得并不多,应该是用于TCP连接的计数和对涉及TCP的修改的定位
3. nat表的实现
-
首先ip_nat_rule_init()(位于net/ipv4/netfilter/ip_nat_rule.c,Line278)调用ipt_register_table()来初始化并注册nat表,然后利用ipt_register_target()来初始化并注册SNAT和DNAT,在这个注册过程中,关键的函数是ip_nat_setup_info(位于net/ipv4/netfilter/ip_nat_core.c,Line511),其工作是:
-
首先调用invert_tupler()(net/ipv4/netfilter/ip_conntrack_core.c,Line879),将记录反转
-
然后调用get_unique_tuple()(net/ipv4/netfilter/ip_nat_core.c,Line393,在指定的地址范围(ip_nat_multi_range结构)中查找空闲的地址),如果没有空闲地址可用则会返回NF_DROP
-
判断源和目的是否改变,如果改变,则更新ip_nat_info。
-
然后ip_nat_init()(位于net/ipv4/netfilter/ip_nat_core.c,Line953)会给nat所用的两个hash表(bysource、byipsproto)分配空间并初始化各种协议
-
最后会通过nf_register_hook()注册相应HOOK的函数ip_nat_fn()、ip_nat_local_fn()和ip_nat_out(),并增加连接跟踪的计数器。
-
它首先在ip_nat_info->manip数组中查找能够匹配的绑定
-
然后调用manip_pkt()函数(位于net/ipv4/netfilter/ip_nat_core.c,Line701)进行相应的地址转换:
-
manip_pkt()这个函数会根据不同的方向进行转换,并且对校验和进行处理
-
同时,它是递归调用自己以处理不同协议的情况
-
最后调用helper模块进行执行(当然,如果有的话),特别是避免在同一个数据报上执行多次同一个helper模块
4. 协议的扩展
struct ip_nat_protocol
{
struct list_head list;
const char *name;
unsigned int protonum;
void (*manip_pkt)(struct iphdr *iph, size_t len,
const struct ip_conntrack_manip *manip,
enum ip_nat_manip_type maniptype);
int (*in_range)(const struct ip_conntrack_tuple *tuple,
enum ip_nat_manip_type maniptype,
const union ip_conntrack_manip_proto *min,
const union ip_conntrack_manip_proto *max);
int (*unique_tuple)(struct ip_conntrack_tuple *tuple,
const struct ip_nat_range *range,
enum ip_nat_manip_type maniptype,
const struct ip_conntrack *conntrack);
unsigned int (*print)(char *buffer,
const struct ip_conntrack_tuple *match,
const struct ip_conntrack_tuple *mask);
unsigned int (*print_range)(char *buffer,
const struct ip_nat_range *range);
};
-
`void (*manip_pkt)(……)`:其指向的函数会根据ip_nat_info->manip参数进行数据报的转换,即do_bindings()中调用的manip_pkt()函数
-
`int (*in_range)(……)`:其指向的函数检查多元组的协议部分值是否在指定的区间之内
-
`int (*unique_tuple)(……)`:其指向函数的作用是根据manip的类型修改多元组中的协议部分,以获得一个唯一的地址,多元组的协议部分被初始化为ORIGINAL方向
八、数据报修改模块──mangle表
1. 概述
-
TOS(服务类型):修改IP数据报头的TOS字段值
-
TTL(生存时间):修改IP数据报头的TTL字段值
-
MARK:修改skb的nfmark域设置的nfmark字段值。
-
nfmark是数据报的元数据之一,是一个用户定义的数据报的标记,可以是unsigned long范围内的任何值。该标记值用于基于策略的路由,通知ipqmpd(运行在用户空间的队列分拣器守护进程)将该数据报排队给哪个进程等信息。
-
-
TCP MSS(最大数据段长度):修改TCP数据报头的MSS字段值
2. mangle表的实现
3. 数据报的修改
struct ipt_tos_info {
u_int8_t tos;
u_int8_t invert;
};
static struct ipt_match tos_match
= { { NULL, NULL }, "tos", &match, &checkentry, NULL, THIS_MODULE };
九、其它高级功能模块
-
REJECT,丢弃包并通知包的发送者,同时返回给发送者一个可配置的ICMP错误信息,由ipt_REJECT.o完成
-
MIRROR,互换源和目的地址以后并重新发送,由ipt_MIRROR.o完成
-
LOG, 将匹配的数据报传递给系统的syslog()进行记录,由ipt_LOG.o完成
-
ULOG,Userspace logging,将数据报排队转发到用户空间中,将匹配的数据适用用户空间的log进程进行记录,由ip_ULOG.o完成。这是Netfilter的一个关键技术,可以使用户进程可以进行复杂的数据报操作,从而减轻内核空间中的复杂度
-
Queuing,这是上面ULOG技术的基础,由ip_queue.o完成,提供可靠的异步包处理以及性能两号的libipq库来进行用户空间数据报操作的开发。
-
等等……