• filter表的实现

filter表的实现函数实际上就是模块iptable_filter.o的init函数,位于net/ipv4/netfilter/iptable_filter.c,Line128。其主要工作是首先通过ipt_register_table()函数进行表的注册,然后用nf_register_hook()函数注册表所监听的各个HOOK。

其中,对HOOK进行注册时,是通过对数据结构nf_hook_ops的一个实例ipt_ops进行操作来实现的,这个实例的定义及初始化位于net/ipv4/netfilter/iptable_filter.c,Line117:

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 }

};

对应前面所分析nf_hook_ops的各个成员,不难确定这些初始化值的意义。

其中,对应IN和FORWARD的处理函数均为ipt_hook,OUT的处理函数则为ipt_local_out_hook,下面依次分析之:

  • 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);

}

实际上它就是调用了ipt_do_table()函数,也就是说,注册时首先注册一个ipt_hook()函数,然后ipt_hook()通过调用ipt_do_table()函数对传入的数据进行真正的处理。下面我们来看一下ipt_do_table()这个函数:

它位于net/ipv4/netfilter/ip_tables.c,Line254,是一个很长的函数,其主要功能是对数据报进行各种匹配、过滤(包括基本规则、matches以及target),具体些说,其工作大致为:

  1. 初始化各种变量,如IP头、数据区、输入输出设备、段偏移、规则入口及偏移量等等;

  2. 进行规则的匹配,首先调用ip_packet_match()函数(位于net/ipv4/netfilter/ip_tables.c,Line121)确定IP数据报是否匹配规则,若不匹配则跳到下一条规则(这个函数的主要工作大致为:依次处理源/目的IP地址、输入输出接口,然后对基本的规则进行匹配);

  3. 如果数据报匹配,则下面开始继续匹配matches和target,首先利用宏IPT_MATCH_ITERATE调用do_match()函数(下面单独分析)对扩展的match进行匹配,若不匹配则跳到下一条规则;

  4. 扩展match匹配后,首先调用ipt_get_target()获得target的偏移地址,然后对target进行匹配,这个匹配的过程要比match的匹配过程复杂一些,同样在下面单独分析。

下面首先来分析do_match()函数,它位于net/ipv4/netfilter/ip_tables.c,Line229,它的实现只有一个if语句:

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(skb, in, out, m->data, offset, hdr, datalen, hotdrop)`是用来定位match的。

因为如果仅仅是根据match的名字遍历链表来进行查找的话,效率会非常低下。Netfilter源码中采用的方法是在进行match的检测之前,也就是在ipt_register_table()函数中通过translate_table()函数由宏IPT_ENTRY_ITERATE调用函数check_entry()时,在check_entry()中通过宏IPT_MATCH_ITERATE调用了check_match()函数(位于net/ipv4/netfilter/ip_tables.c,Line640),在这个函数中,有一个对m->u.kernel.match的赋值:

m->u.kernel.match = match;

这样,每条规则的u.kernel.match 就与内核模块中的struct ipt_match链表关联了起来,也就是说,这样一来,根据match的名字,其对应的match函数就与链表中对应的函数关联了起来。于是,上面的那条定位match的语句的意义也就开始明了了:

利用宏IPT_MATCH_ITERATE来遍历规则中的所有mach,然后直接调用`m->u.kernel.match->match`来进行对数据报的匹配工作——这样的效率显然要比简单的遍历要高许多。

然后我们来看一下对target的匹配,从数据结构的实现上看,似乎这个过程与match的匹配应该是相似的,但实际上target存在标准的和非标准的两种,其中标准的target与非标准的target的处理是不一样的。在这里我遇到了问题,如下:

首先,在Netfilter的源码中,存在两个ipt_standard_target,其中一个是一个struct,位于include/linux/netfilter_ipv4/ip_tables.h,Line94;另一个是`struct ipt_target`的一个实例,位于net/ipv4/netfilter/IPtables.c,Line1684,而在target的匹配过程中,它是这样处理的(ipt_do_tables(),net/ipv4/netfilter/ip_tables.c,Line329):

if (!t->u.kernel.target->target) {……}

从这里看来,它应该是当 t->u.kernel.target的target函数为空时,表明其为标准的target。那么结合上述两者的定义,似乎用的是后者,因为后者的定义及初始化如下:

static struct ipt_target ipt_standard_target

= { { NULL, NULL }, IPT_STANDARD_TARGET, NULL, NULL, NULL };

但是问题出现在:初始化中的IPT_STANDARD_TARGET被定义为””!!并且在整个源码中,用到实例化的ipt_standard_target的地方仅有两处,即上面的这个定义以及ip_tables.c中将ipt_standard_target加入到target链表之中。也就是说这个实例的名字一直为空,这一点如何理解?

  • ipt_local_out_hook,定义于net/ipv4/netfilter/iptable_filter.c,Line99其功能与ipt_hook()相似,只不过为了防止DOS攻击而增加了对ratelimit的检查。

这样,到这里,filter表的实现已经分析完毕,至于具体的过滤功能如何实现,那就是每个HOOK处理函数的问题了。

六、连接跟踪模块(Conntrack)

1. 概述

连接跟踪模块是NAT的基础,但作为一个单独的模块实现。它用于对包过滤功能的一个扩展,管理单个连接(特别是TCP连接),并负责为现有的连接分配输入、输出和转发IP数据报,从而基于连接跟踪来实现一个“基于状态”的防火墙。

当连接跟踪模块注册一个连接建立包之后,将生成一个新的连接记录。此后,所有属于此连接的数据报都被唯一地分配给这个连接。如果一段时间内没有流量而超时,连接将被删除,然后其他需要使用连接跟踪的模块就可以重新使用这个连接所释放的资源了。

如果需要用于比传输协议更上层的应用协议,连接跟踪模块还必须能够将建立的数据连接与现有的控制连接相关联。

Conntrack在Netfilter中是基于下列HOOK的:

  • NF_IP_PRE_ROUTING

  • NF_IP_LOCAL_OUT

  • 同时当使用NAT时,Conntrack也会有基于NF_IP_LOCAL_IN和NF_IP_POST_ROUTING的,只不过优先级很小。

在所有的HOOK上,NF_IP_PRI_CONNTRACK的优先级是最高的(-200),这意味着每个数据报在进入和发出之前都首先要经过Conntrack模块,然后才会被传到钩在HOOK上的其它模块。

Conntrack模块的接口位于文件net/ipv4/netfilter/ip_conntrack_standalone.c中。

2. 连接状态的管理

  1. 多元组

在连接跟踪模块中,使用所谓的“tuple”,也就是多元组,来小巧锐利地描述连接记录的关键部分,主要是方便连接记录的管理。其对应的数据结构是ip_conntrack_tuple(位于include/linux/netfilter_ipv4/ip_conntrack_tuple.h,Line38):

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;

};

从它的定义可以看出,一个多元组实际上包括两个部分:一是所谓的“unfixed”部分,也就是源地址以及端口;二是所谓的“fixed”部分,也就是目的地址、端口以及所用的协议。这样,连接的两端分别用地址+端口,再加上所使用的协议,一个tuple就可以唯一地标识一个连接了(对于没有端口的icmp协议,则用其它东东标识)。

  1. 连接记录

那么真正的完整连接记录则是由数据结构ip_conntrack(位于include/linux/netfilter_ipv4/ip_conntrack.h,Line160)来描述的,其成员有:

  • `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模块设置的信息(在条件编译中)

  1. hash表

Netfilter使用一个hash表来对连接记录进行管理,这个hash表的初始指针为*ip_conntrack_hash,位于net/ipv4/netfilter/ip_conntrack_core.c,Line65,这样我们就可以使用ip_conntrack_hash[num]的方式来直接定位某个连接记录了。

而hash表的每个表项则由数据结构ip_conntrack_tuple_hash(位于include/linux/netfilter_ipv4/ip_conntrack_tuple.h,Line86)描述 :

struct ip_conntrack_tuple_hash

{

struct list_head list;

struct ip_conntrack_tuple tuple;

struct ip_conntrack *ctrack;

};

可见,一个hash表项中实质性的内容就是一个多元组ip_conntrack_tuple;同时还有一个指向连接的ip_conntrack结构的指针;以及一个链表头(这个链表头不知是干嘛的)。

3. 连接跟踪的实现

有了以上的数据结构,连接跟踪的具体实现其实就非常简单而常规了,无非就是初始化、连接记录的创建、在连接跟踪hash表中搜索并定位数据报、将数据报转换为一个多元组、判断连接的状态以及方向、超时处理、协议的添加、查找和注销、对不同协议的不同处理、以及在两个连接跟踪相关的HOOK上对数据报的处理等。

下面重点说明一下我在分析中遇到的几个比较重要或者比较难理解的地方:

  • 所谓“预期链接”

可以将预期连接看作父子关系来理解,如图  http://blog.chinaunix.net/photo/24896_061206192612.jpg

  • ip_conntrack的状态转换

ip_conntrack的状态转换分两种,同样用图来描述。 首先是正常的状态转换,如图http://blog.chinaunix.net/photo/24896_061206192631.jpg,
        然后是 ICMP error时的状态转换(由函数icmp_error_track()来判断,位于net/ipv4/netfilter/ip_conntrack_core.c,Line495),
        如图 http://blog.chinaunix.net/photo/24896_061206192648.jpg

  • 在NF_IP_PRE_ROUTING上对分片IP数据报的处理

在经过 HOOK 中的 NF_IP_PRE_ROUTING 时(函数 ip_conntrack_in() ,位于 net/ipv4/netfilter/ip_conntrack_core.c , Line796 ),由于外来的数据报有可能是经过分片的,所以必须对分片的情形进行处理,将 IP 数据报组装后才能分配给连接。
    具体的操作是首先由函数 ip_ct_gather_frags() 对分片的数据报进行收集,然后调用 ip_defrag() 函数(位于 net/ipv4/ip_fragment.c , Line632 )组装之

4. 协议的扩展

由于我们可能要添加新的协议,所以单独对协议的扩展进行分析。

各种协议使用一个全局的协议列表存放,即protocol_list(位于include/linux/netfilter_ipv4/ip_conntrack_core.h,Line21),使用结构ip_conntrack_protocol(位于include/linux/netfilter_ipv4/ip_conntrack_protocol.h,Line6)来表示:

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)(……)`:其指向函数的作用是判断是否有数据报匹配预期的连接

添加/删除协议使用的是函数ip_conntrack_protocol_register()以及ip_conntrack_protocol_unregister(),分别位于net/ipv4/netfilter/ip_conntrack_standalone.c,Line298 & 320,其工作就是将ip_conntrack_protocol添加到全局协议列表protocol_list。

七、网络地址转换模块(Network Address Translation)

1. 概述

网络地址转换的机制一般用于处理IP地址转换,在Netfilter中,可以支持多种NAT类型,而其实现的基础是连接跟踪。

NAT可以分为SNAT和DNAT,即源NAT和目的NAT,在Netfilter中分别基于以下HOOK:

  • 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规则。

同时,MASQUERADE(伪装)是SNAT的一种特例,它与SNAT几乎一样,只有一点不同:如果连接断开,所有的连接跟踪信息将被丢弃,而去使用重新连接以后的IP地址进行IP伪装;而REDIRECT(重定向)是DNAT的一种特例,这时候就相当于将符合条件的数据报的目的IP地址改为数据报进入系统时的网络接口的IP地址。

2. 基于连接跟踪的相关数据结构

NAT是基于连接跟踪实现的,NAT中所有的连接都由连接跟踪模块来管理,NAT模块的主要任务是维护nat表和进行实际的地址转换。这样,我们来回头重新审视一下连接跟踪模块中由条件编译决定的部分。

首先,是连接的描述ip_conntrack,在连接跟踪模块部分中提到,这个结构的最后有“为NAT模块设置的信息”,即:

#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成员

好,下面我们来看一下这个ip_nat_info结构,这个结构存储了连接中的地址绑定信息,其定义位于include/linux/netfilter_ipv4/ip_nat.h,Line98:

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表的实现

nat表的初始化和实现与filter极为相似。

在初始化上,其初始化位于net/ipv4/netfilter/ip_nat_rule.c,Line104,初始化所用模板则位于net/ipv4/netfilter/ip_nat_rule.c,Line50。

在实现上,其实现函数就是NAT模块的初始化函数init_or_cleanup()(位于net/ipv4/netfilter/ip_nat_standalone.c,Line278)。其工作主要是依次调用ip_nat_rule_init()、ip_nat_init()以及nf_register_hook()。

  1. 首先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。

  1. 然后ip_nat_init()(位于net/ipv4/netfilter/ip_nat_core.c,Line953)会给nat所用的两个hash表(bysource、byipsproto)分配空间并初始化各种协议

  2. 最后会通过nf_register_hook()注册相应HOOK的函数ip_nat_fn()、ip_nat_local_fn()和ip_nat_out(),并增加连接跟踪的计数器。

在具体的HOOK函数实现上,后两者其实都是基于ip_nat_fn()的,而这其中最重要的处理函数,也就是实际的网络地址转换函数是do_bindings(),下面将对其进行分析:

do_bindings()位于net/ipv4/netfilter/ip_nat_core.c,Line747,其主要工作是将ip_nat_info中的地址绑定应用于数据报:

  1. 它首先在ip_nat_info->manip数组中查找能够匹配的绑定

  2. 然后调用manip_pkt()函数(位于net/ipv4/netfilter/ip_nat_core.c,Line701)进行相应的地址转换:

  • manip_pkt()这个函数会根据不同的方向进行转换,并且对校验和进行处理

  • 同时,它是递归调用自己以处理不同协议的情况

  1. 最后调用helper模块进行执行(当然,如果有的话),特别是避免在同一个数据报上执行多次同一个helper模块

nat表与filter表还有一个较大的不同:在一个连接中,只有第一个数据报才会经过nat表,而其转换的结果会作用于此连接中的其它所有数据报。

4. 协议的扩展

要想扩展NAT的协议,那么必须写入两个模块,一个用于连接跟踪,一个用于NAT实现。

与连接跟踪类似,nat中协议也由一个列表protos存放,位于include/linux/netfilter_ipv4/ip_nat_core.h,Line17。协议的注册和注销分别是由函数ip_nat_protocol_register()和ip_nat_protocol_unregister()实现的,位于net/ipv4/netfilter/ip_nat_standalone.c,Line242。

nat的协议是由结构ip_nat_protocol描述的,其定义位于include/linux/netfilter_ipv4/ip_nat_protocol.h,Line10:

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. 概述

mangle这个词的原意是撕裂、破坏,这里所谓“packet mangling”是指对packet的一些传输特性进行修改。mangle表被用来真正地对数据报进行修改,它可以在所有的5个HOOK上进行操作。

从源码看来,在mangle表中所允许修改的传输特性目前有:

  • TOS(服务类型):修改IP数据报头的TOS字段值

  • TTL(生存时间):修改IP数据报头的TTL字段值

  • MARK:修改skb的nfmark域设置的nfmark字段值。

    • nfmark是数据报的元数据之一,是一个用户定义的数据报的标记,可以是unsigned long范围内的任何值。该标记值用于基于策略的路由,通知ipqmpd(运行在用户空间的队列分拣器守护进程)将该数据报排队给哪个进程等信息。

  • TCP MSS(最大数据段长度):修改TCP数据报头的MSS字段值

2. mangle表的实现

遍历整个源码,没有发现mangle表的独有数据结构。

mangle表的初始化和实现与filter极为相似。在初始化上,其初始化位于net/ipv4/netfilter/iptable_mangle.c,Line117,初始化所用模板则位于net/ipv4/netfilter/iptable_mangle.c,Line43。

在实现上,其实现函数就是mangle模块的初始化函数init()(位于net/ipv4/netfilter/iptable_mangle.c,Line183)。其工作就是依次注册packet_mangler表以及5个HOOK。其中NF_IP_LOCAL_OUT的处理函数为ipt_local_hook(),其余均为ipt_route_hook(),分别位于net/ipv4/netfilter/iptable_mangle.c,Line132 & 122,二者的关键实现都是通过调用ipt_do_table()实现的。

3. 数据报的修改

对数据报不同位的修改都是通过单独的模块实现的,也就是说由ipt_tos.o、ipt_ttl.o、ipt_mark.o、ipt_tcpmss.o实现上面所说的四种传输特性的修改。

以TOS为例,其涉及的文件为net/ipv4/netfilter/ipt_tos.c以及include/linux/netfilter_ipv4/ipt_tos.h。

其专有数据结构为ipt_tos_info,定义于ipt_tos.h中:

struct ipt_tos_info {

u_int8_t tos;

u_int8_t invert;

};

其模块的添加/卸载函数很简单,其实就是添加/删除TOS的MATCH:tos_match(定义并初始化于ipt_tos.c,Line37):

static struct ipt_match tos_match

= { { NULL, NULL }, "tos", &match, &checkentry, NULL, THIS_MODULE };

而在tos_match的处理函数match()中,已经完成了对相应位的赋值,这似乎是违反match仅仅匹配而不修改的一个特例。

九、其它高级功能模块

Netfilter中还有一些其它的高级功能模块,基本是为了用户操作方便的,没有对它们进行分析,如:

  • 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库来进行用户空间数据报操作的开发。

  • 等等……

iptable 简析相关推荐

  1. 【Golang源码分析】Go Web常用程序包gorilla/mux的使用与源码简析

    目录[阅读时间:约10分钟] 一.概述 二.对比: gorilla/mux与net/http DefaultServeMux 三.简单使用 四.源码简析 1.NewRouter函数 2.HandleF ...

  2. 简析平衡树(三)——浅谈Splay

    前言 原本以为\(Treap\)已经很难了,学习了\(Splay\),我才知道,没有最难,只有更难.(强烈建议先去学一学\(Treap\)再来看这篇博客) 简介 \(Splay\)是平衡树中的一种,除 ...

  3. 基于libmad库的MP3解码简析

    基于libmad库的MP3解码简析  MAD (libmad)是一个开源的高精度 MPEG 音频解码库,支持 MPEG-1(Layer I, Layer II 和 LayerIII(也就是 MP3). ...

  4. 简析 .NET Core 构成体系

    简析 .NET Core 构成体系 Roslyn 编译器 RyuJIT 编译器 CoreCLR & CoreRT CoreFX(.NET Core Libraries) .NET Core 代 ...

  5. Python源码学习:内建类型简析并简析int对象

    Python源码分析 本文环境python2.5系列 参考书籍<<Python源码剖析>> 上一篇文章中已经大致分析了下,Python的启动执行流程,现在我们分析一下Pytho ...

  6. Python源码学习:启动流程简析

    Python源码分析 本文环境python2.5系列 参考书籍<<Python源码剖析>> Python简介: python主要是动态语言,虽然Python语言也有编译,生成中 ...

  7. 简析TCP的三次握手与四次分手【转】

    转自 简析TCP的三次握手与四次分手 | 果冻想 http://www.jellythink.com/archives/705 TCP是什么? 具体的关于TCP是什么,我不打算详细的说了:当你看到这篇 ...

  8. ceph存储原理_Ceph存储引擎BlueStore简析

    前文我们创建了一个单节点的Ceph集群,并且创建了2个基于BlueStore的OSD.同时,为了便于学习,这两个OSD分别基于不同的布局,也就是一个OSD是基于3中不同的存储介质(这里是模拟的,并非真 ...

  9. Android Jetpack组件App Startup简析

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

  10. Webpack模块化原理简析

    webpack模块化原理简析 1.webpack的核心原理 一切皆模块:在webpack中,css,html.js,静态资源文件等都可以视作模块:便于管理,利于重复利用: 按需加载:进行代码分割,实现 ...

最新文章

  1. 8月25号 工作计划与实行
  2. Step by Step to download a material from ERP via request download
  3. tomcat对于web.xml的security-constraint使用的处理机制
  4. colorkey唇釉是否安全_好物推荐|哇哦!有被这些唇釉美到耶
  5. ARM裸机工作笔记0001---ARM那些事
  6. 《通信原理》复习笔记6----第六章数字基带传输系统(重中之重点+难上加难点)
  7. python读取海康威视摄像头价格_OpenCV+海康威视摄像头的实时读取
  8. 2011戴尔计算机配置,机型与配置(一)
  9. 优化算法 | 多车型车辆路径问题-初始解构造方法
  10. 网络安全技能竞赛通关教程
  11. java netty 内存泄露_Netty开发调试设置io.netty.leakDetection.level=PARANOID定位内存泄漏问题...
  12. linux没有cpufreq目录,Linux内核的cpufreq(变频)机制
  13. 计算机网络——ALOHA协议
  14. linux虚拟机防火墙关不了怎么办,虚拟机centOS7 关闭防火墙后ping通 telnet不通 解决办法:disable seLinux...
  15. 权限控制框架 shiro
  16. 2022起重机司机(限桥式起重机)特种作业证考试题库及在线模拟考试
  17. 虚拟化服务器里的cpu是什么型号的,VMware虚拟化CPU型号不一样,在集群中如何进行VMotion?...
  18. 优麒麟 19.04 即将发布,华为、阿里云、重大、360四大境像站鼎力支持!
  19. IDEA settings.xml 阿里云配置备份
  20. 【论文阅读|深读】LINE: Large-scale Information Network Embedding

热门文章

  1. J2EE是什么(二)
  2. 计算机网络被限速,电脑网速被限制怎么办
  3. 16种常用的数据分析方法-相关分析
  4. 适配层java接口_Linux Framebuffer适配层释疑
  5. appium java模拟微信登录,使用Appium 测试微信小程序和微信公众号方法
  6. 操作系统——实验一(Linux基本操作)
  7. 网络编程:Socket编程从IPv4转向IPv6支持
  8. IPv4到IPv6的改造转换方案(上):IPv6和IPv4优势对比
  9. 顺序表和链表 相关知识点总结
  10. Android smali语法