重读nat

时间:2021-01-09 00:53:39

  目前内核NAT 是基于nf_conntrack连接跟踪实现。

首先看下conntrack的相关知识!

/*
struct sk_buff {

struct nf_conntrack *nfct;//指向struct nf_conn实例
..............
};
*/
//最主要的就是tuplehash(跟踪连接双方向数据)和status(记录连接状态)
struct nf_conn {//每个struct nf_conn实例代表一个连接。每个skb都有一个指针,指向和它相关联的连接。
/* Usage count in here is 1 for hash table/destruct timer, 1 per skb,
* plus 1 for any connection(s) we are `master' for
*
* Hint, SKB address this struct and refcnt via skb->nfct and
* helpers nf_conntrack_get() and nf_conntrack_put().
* Helper nf_ct_put() equals nf_conntrack_put() by dec refcnt,
* beware nf_ct_get() is different and don't inc refcnt.
*/
struct nf_conntrack ct_general; //对连接的引用计数

spinlock_t lock;
u16 cpu;

/* These are my tuples; original and reply */
/* Connection tracking(链接跟踪)用来跟踪、记录每个链接的信息(目前仅支持IP协议的连接跟踪)。
每个链接由“tuple”来唯一标识,这里的“tuple”对不同的协议会有不同的含义,例如对tcp,udp
来说就是五元组: (源IP,源端口,目的IP, 目的端口,协议号),对ICMP协议来说是: (源IP, 目
的IP, id, type, code), 其中id,type与code都是icmp协议的信息。链接跟踪是防火墙实现状态检
测的基础,很多功能都需要借助链接跟踪才能实现,例如NAT、快速转发、等等。 */

struct nf_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX];//正向和反向的连接元组信息。
/* 这是一个位图,是一个状态域。在实际的使用中,它通常与一个枚举类型ip_conntrack_status(位于include/linux/netfilter_ipv4/ip_conntrack.h,Line33)
进行位运算来判断连接的状态。其中主要的状态包括:

IPS_EXPECTED(_BIT),表示一个预期的连接

IPS_SEEN_REPLY(_BIT),表示一个双向的连接

IPS_ASSURED(_BIT),表示这个连接即使发生超时也不能提早被删除

IPS_CONFIRMED(_BIT),表示这个连接已经被确认(初始包已经发出) */
/* 可以设置由enum ip_conntrack_status中描述的状态 */
/* Have we seen traffic both ways yet? (bitset) */
unsigned long status;//该连接的连接状态 由enum ip_conntrack_status中描述的状态

/* Timer function; drops refcnt when it goes off. */
struct timer_list timeout; //连接垃圾回收定时器 连接跟踪的超时时间

possible_net_t ct_net;

/* all members below initialized via memset */
u8 __nfct_init_offset[0];
/*结构ip_conntrack_expect位于ip_conntrack.h,这个结构用于将一个预期的连接分配给现有的连接,也就是说本连接是这个master的一个预期连接*/
/* If we were expected by an expectation, this will be it */
struct nf_conn *master;//如果该连接是期望连接,指向跟其关联的主连接

#if defined(CONFIG_NF_CONNTRACK_MARK)
u_int32_t mark;
#endif

#ifdef CONFIG_NF_CONNTRACK_SECMARK
u_int32_t secmark;
#endif

/* Extensions */ /*指向扩展结构,该结构中包含一些基于连接的功能扩展处理函数 */
struct nf_ct_ext *ext;

/* Storage reserved for other modules, must be the last member */
union nf_conntrack_proto proto; /*存储特定协议的连接跟踪信息 也就是不同协议实现连接跟踪的额外参数 */
};
/* Connection state tracking for netfilter.  This is separated from,
but required by, the NAT layer; it can also be used by an iptables
extension. */
enum ip_conntrack_info {
/* Part of an established connection (either direction). 表示这个数据包对应的连接在两个方向都有数据包通过,
并且这是ORIGINAL初始方向数据包(无论是TCP、UDP、ICMP数据包,
只要在该连接的两个方向上已有数据包通过,就会将该连接设置为IP_CT_ESTABLISHED状态。不会根据协议中的标志位进行判断,
例如TCP的SYN等)。但它表示不了这是第几个数据包,也说明不了这个CT是否是子连接。*/
IP_CT_ESTABLISHED,

/* Like NEW, but related to an existing connection, or ICMP error
(in either direction). 表示这个数据包对应的连接还没有REPLY方向数据包,当前数据包是ORIGINAL方向数据包。
并且这个连接关联一个已有的连接,是该已有连接的子连接,? ??tatus标志中已经设置了IPS_EXPECTED标志,该标志在init_conntrack()函数中设置)。但无法
判断是第几个数据包(不一定是第一个)*/
IP_CT_RELATED,

/* Started a new connection to track (only
IP_CT_DIR_ORIGINAL); may be a retransmission.
表示这个数据包对应的连接还没有REPLY方向数据包,当前数据包是ORIGINAL方向数据包,该连接不是子连接。但无法判断是
第几个数据包(不一定是第一个*/
IP_CT_NEW,

/* >= this indicates reply direction这个状态一般不单独使用,通常以下面两种方式使用 */
IP_CT_IS_REPLY,
/* 表示这个数据包对应的连接在两个方向都有数据包通过,并且这是REPLY应答方向数据包。但它表示不了这是
第几个数据包,也说明不了这个CT是否是子连接。*/
IP_CT_ESTABLISHED_REPLY = IP_CT_ESTABLISHED + IP_CT_IS_REPLY,
/*这个状态仅在nf_conntrack_attach()函数中设置,用于本机返回REJECT,例如返回一个ICMP目的不可达报文,
或返回一个reset报文。它表示不了这是第几个数据包
*/
IP_CT_RELATED_REPLY = IP_CT_RELATED + IP_CT_IS_REPLY,
/* No NEW in reply direction. */

/* Number of distinct IP_CT types. */
IP_CT_NUMBER,
};
/* Bitset representing status of connection. */
enum ip_conntrack_status {
/* It's an expected connection: bit 0 set. This bit never changed */
IPS_EXPECTED_BIT = 0,//表示该连接是个子连接
IPS_EXPECTED = (1 << IPS_EXPECTED_BIT),

/* We've seen packets both ways: bit 1 set. Can be set, not unset. */
IPS_SEEN_REPLY_BIT = 1,//表示该连接上双方向上都有数据包了
IPS_SEEN_REPLY = (1 << IPS_SEEN_REPLY_BIT),

/* Conntrack should never be early-expired.
TCP:在三次握手建立完连接后即设定该标志。UDP:如果在该连接上的两个方向都有数据包通过,则再有数据包在该连接上通过时? 就设定该标志。ICMP:不设置该标志
*/
IPS_ASSURED_BIT = 2,

IPS_ASSURED = (1 << IPS_ASSURED_BIT),

/* Connection is confirmed: originating packet has left box
表示该连接已被添加到net->ct.hash表*/
IPS_CONFIRMED_BIT = 3,
IPS_CONFIRMED = (1 << IPS_CONFIRMED_BIT),

/* Connection needs src nat in orig dir. This bit never changed.
在POSTROUTING处,当替换reply tuple完成时, 设置该标记*/
IPS_SRC_NAT_BIT = 4,
IPS_SRC_NAT = (1 << IPS_SRC_NAT_BIT),

/* Connection needs dst nat in orig dir. This bit never changed.
在PREROUTING处,当替换reply tuple完成时, 设置该标记*/
IPS_DST_NAT_BIT = 5,
IPS_DST_NAT = (1 << IPS_DST_NAT_BIT),

/* Both together. */
IPS_NAT_MASK = (IPS_DST_NAT | IPS_SRC_NAT),

/* Connection needs TCP sequence adjusted. */
IPS_SEQ_ADJUST_BIT = 6,
IPS_SEQ_ADJUST = (1 << IPS_SEQ_ADJUST_BIT),

/* NAT initialization bits. 在POSTROUTING处,已被SNAT处理,并被加入到bysource链中,设置该标记*/
IPS_SRC_NAT_DONE_BIT = 7,
IPS_SRC_NAT_DONE = (1 << IPS_SRC_NAT_DONE_BIT),

IPS_DST_NAT_DONE_BIT = 8,//在PREROUTING处,已被DNAT处理,并被加入到bysource链中,设置该标记
IPS_DST_NAT_DONE = (1 << IPS_DST_NAT_DONE_BIT),

/* Both together */
IPS_NAT_DONE_MASK = (IPS_DST_NAT_DONE | IPS_SRC_NAT_DONE),

/* Connection is dying (removed from lists), can not be unset.
表示该连接正在被释放,内核通过该标志保证正在被释放的ct不会被其它地方再次引用。有了这个标志,当某个连接要被删
除时,即使它还在net->ct.hash中,也不会再次被引 用*/
IPS_DYING_BIT = 9,
IPS_DYING = (1 << IPS_DYING_BIT),

/* Connection has fixed timeout.
固定连接超时时间,这将不根据状态修改连接超时时间。通过函数nf_ct_refresh_acct()修改超时时间时检查该标志*/
IPS_FIXED_TIMEOUT_BIT = 10,
IPS_FIXED_TIMEOUT = (1 << IPS_FIXED_TIMEOUT_BIT),

/* Conntrack is a template
由CT target进行设置(这个target只能用在raw表中,用于为数据包构建指定ct,并打上该标志),用于表明这个ct是由CT target创建*/
IPS_TEMPLATE_BIT = 11,
IPS_TEMPLATE = (1 << IPS_TEMPLATE_BIT),

/* Conntrack is a fake untracked entry */
IPS_UNTRACKED_BIT = 12,
IPS_UNTRACKED = (1 << IPS_UNTRACKED_BIT),

/* Conntrack got a helper explicitly attached via CT target. */
IPS_HELPER_BIT = 13,
IPS_HELPER = (1 << IPS_HELPER_BIT),
};

三层协议(IPv4/IPv6)

利用nf_conntrack_proto.c文件中的nf_conntrack_l3proto_registe 注册三层协议处理函数

 

重读nat

 

 四层协议(TCP/UDP)

 利用 nf_conntrack_l4proto_register(struct nf_conntrack_l4proto *l4proto) 注册相关函数

重读nat

 

 

处理一个连接的子连接协议

  利用nf_conntrack_helper.c文件中的​​nf_conntrack_helper_register(struct nf_conntrack_helper *me)以及 nf_ct_expect_related_report(struct nf_conntrack_expect *expect, u32 pid, int report)​

​注册相关函数​

重读nat

 

 扩展连接跟踪结构(nf_conn)

 

利用nf_ct_extend_register(struct nf_ct_ext_type *type) 进行扩展,并修改连接跟踪相应代码来利用这部分扩展功能;

重读nat

 

 nf_conntrack模块加载时的初始化流程

重读nat

 

 

nf_conntrack的初始化

就是初始化上面提到的那些数据结构,它在内核启动时调用nf_conntrack_standalone_init()函数进行初始化的。初始化完成后,构建出如下图所示的结构图,只是不包含下图中与连接有关的信息(nf_conn和nf_conntrack_expect结构)

重读nat

 

 当创建子连接时,各个数据结构之间的关系

重读nat

 

 

IPv4-NAT连接跟踪相关部分通过函数nf_nat_init()初始化

调用nf_ct_extend_register() 注册一个连接跟踪的扩展功能。

重读nat

 

  调用register_pernet_subsys() –> nf_nat_net_init() 创建net->ipv4.nat_bysource的HASH表,大小等于net->ct.htable_size。

初始化nf_nat_protos[]数组,为TCP、UDP、ICMP协议指定专用处理结构,其它协议都指向默认处理结构

重读nat

 

 

为nf_conntrack_untracked连接设置IPS_NAT_DONE_MASK标志。

将NAT模块的全局变量l3proto指向IPV4协议的nf_conntrack_l3proto结构。

设置全局指针nf_nat_seq_adjust_hook指向nf_nat_seq_adjust()函数。

设置全局指针nfnetlink_parse_nat_setup_hook指向nfnetlink_parse_nat_setup()函数。

设置全局指针nf_ct_nat_offset指向nf_nat_get_offset()函数。

IPv4-NAT功能的iptables部分通过函数nf_nat_standalone_init()初始化

调用nf_nat_rule_init() –> nf_nat_rule_net_init()在iptables中注册一个NAT表

调用 nf_nat_rule_init() 注册SNAT target和DNAT target(通过xt_register_target()函数)

重读nat

 

 调用nf_register_hooks() 挂载NAT的HOOK函数,橙色部分为NAT挂载的HOOK函数

重读nat

 

 IPv4-NAT的主要是通过nf_nat_ipv4_fn()钩子函数处理的

unsigned int
nf_nat_ipv4_fn(void *priv, struct sk_buff *skb, const struct nf_hook_state *state,
unsigned int (*do_chain)(void *priv, struct sk_buff *skb, const struct nf_hook_state *state,struct nf_conn *ct))
{
struct nf_conn *ct;
enum ip_conntrack_info ctinfo;
struct nf_conn_nat *nat;
/* maniptype == SRC for postrouting. */
enum nf_nat_manip_type maniptype = HOOK2MANIP(state->hook);

/* We never see fragments: conntrack defrags on pre-routing
* and local-out, and nf_nat_out protects post-routing.
*/
NF_CT_ASSERT(!ip_is_fragment(ip_hdr(skb)));

ct = nf_ct_get(skb, &ctinfo);
/* Can't track? It's not due to stress, or conntrack would
* have dropped it. Hence it's the user's responsibilty to
* packet filter it out, or implement conntrack/NAT for that
* protocol. 8) --RR
*/
if (!ct)
return NF_ACCEPT;

/* Don't try to NAT if this packet is not conntracked */
if (nf_ct_is_untracked(ct))/*如果该conntrack是确认状态,并且没有nat扩展功能,就不需要处理NAT*/
return NF_ACCEPT;

nat = nf_ct_nat_ext_add(ct);
if (nat == NULL)
return NF_ACCEPT;

switch (ctinfo) {
case IP_CT_RELATED:
case IP_CT_RELATED_REPLY:
if (ip_hdr(skb)->protocol == IPPROTO_ICMP) {
if (!nf_nat_icmp_reply_translation(skb, ct, ctinfo,
state->hook))
return NF_DROP;
else
return NF_ACCEPT;
}
/* Fall thru... (Only ICMPs can be IP_CT_IS_REPLY) */
case IP_CT_NEW:
/* Seen it before? This can happen for loopback, retrans,
* or local packets.
*/
if (!nf_nat_initialized(ct, maniptype)) {
unsigned int ret;
/*查找NAT表并进行报文的处理,会调用NAT target的处理函数对conntrack进行设置*/
ret = do_chain(priv, skb, state, ct);//=== iptable_nat_do_chain(priv, skb, state, ct) --> ipt_snat_target or ipt_dnat_target
if (ret != NF_ACCEPT)
return ret;

if (nf_nat_initialized(ct, HOOK2MANIP(state->hook)))
break;
/*
如果NAT表中没有配置匹配该报文的NAT规则,,就根据报文的ip地址设置一
个不进行NAT转换的NAT规则,这样做的目的是避免该连接上的后续报文都进行
NAT表的查询匹配操作。
*/
ret = nf_nat_alloc_null_binding(ct, state->hook);//--->nf_nat_setup_info
if (ret != NF_ACCEPT)
return ret;
} else {
pr_debug("Already setup manip %s for ct %p\n",
maniptype == NF_NAT_MANIP_SRC ? "SRC" : "DST",
ct);
if (nf_nat_oif_changed(state->hook, ctinfo, nat,
state->out))
goto oif_changed;
}
break;

default:
/* ESTABLISHED */
NF_CT_ASSERT(ctinfo == IP_CT_ESTABLISHED ||
ctinfo == IP_CT_ESTABLISHED_REPLY);
if (nf_nat_oif_changed(state->hook, ctinfo, nat, state->out))
goto oif_changed;
}

return nf_nat_packet(ct, ctinfo, state->hook, skb);

oif_changed:
nf_ct_kill_acct(ct, ctinfo, skb);
return NF_DROP;
}

重读nat

 

 nf_nat_setup_info()函数进一步描述

 

static unsigned int
__nf_nat_alloc_null_binding(struct nf_conn *ct, enum nf_nat_manip_type manip)
{
/* Force range to this IP; let proto decide mapping for
* per-proto parts (hence not IP_NAT_RANGE_PROTO_SPECIFIED).
* Use reply in case it's already been mangled (eg local packet).
*/ /* 使用应答方向的ip地址,LOCAL_OUT会先经过mangle,可能改变了 */
union nf_inet_addr ip =
(manip == NF_NAT_MANIP_SRC ?
ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u3 :
ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.u3);
struct nf_nat_range range = {
.flags = NF_NAT_RANGE_MAP_IPS,
.min_addr = ip,
.max_addr = ip,
};/*该函数只是调用nf_nat_setup_info函数

给conntrack设置status的NAT标志位*/
return nf_nat_setup_info(ct, &range, manip);
}
unsigned int
nf_nat_setup_info(struct nf_conn *ct,
const struct nf_nat_range *range,
enum nf_nat_manip_type maniptype)
{
struct net *net = nf_ct_net(ct);
struct nf_conntrack_tuple curr_tuple, new_tuple;
struct nf_conn_nat *nat;

/* nat helper or nfctnetlink also setup binding */
nat = nf_ct_nat_ext_add(ct);
if (nat == NULL)
return NF_ACCEPT;
/* 获取是进行DNAT还是SNAT,其中PRE_ROUTING和LOCAL_OUT进行DNAT,LOCAL_IN和POST_ROUTING进行SNAT */
NF_CT_ASSERT(maniptype == NF_NAT_MANIP_SRC ||
maniptype == NF_NAT_MANIP_DST);
BUG_ON(nf_nat_initialized(ct, maniptype));

/* What we've got will look like inverse of reply. Normally
* this is what is in the conntrack, except for prior
* manipulations (future optimization: if num_manips == 0,
* orig_tp = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple)
*/
/* 从应答tuple反向得到当前tuple */
nf_ct_invert_tuplepr(&curr_tuple,
&ct->tuplehash[IP_CT_DIR_REPLY].tuple);

/* 根据当前tuple和range得到NAT转换之后的的tuple */
get_unique_tuple(&new_tuple, &curr_tuple, range, ct, maniptype);


/* NAT转换之后和之前的tuple不同 */
if (!nf_ct_tuple_equal(&new_tuple, &curr_tuple)) {
struct nf_conntrack_tuple reply;

/* Alter conntrack table so will recognize replies. */
nf_ct_invert_tuplepr(&reply, &new_tuple); /* 通过新tuple得到reply_tuple */
nf_conntrack_alter_reply(ct, &reply);
/* 此时tuple类似如下 */
/*
//内网10.1通过100.1访问200.1,经过SNAT之后得到tuple
tuple SNAT(10.1->200.1, 200.1->100.1)

//外网300.1通过100.1访问20.1,经过DNAT之后,得到tuple
tuple DNAT(300.1->100.1, 20.1->300.1)
*/
/* Non-atomic: we own this at the moment. */
if (maniptype == NF_NAT_MANIP_SRC)
ct->status |= IPS_SRC_NAT;
else
ct->status |= IPS_DST_NAT;

if (nfct_help(ct)) /* 扩展项的调整 比如 alg ftp sip 调整序列号 */
nfct_seqadj_ext_add(ct);
}

if (maniptype == NF_NAT_MANIP_SRC) { /* SNAT */
unsigned int srchash;

srchash = hash_by_src(net,
&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
spin_lock_bh(&nf_nat_lock);
/* nf_conntrack_alter_reply might re-allocate extension aera */
nat = nfct_nat(ct);
nat->ct = ct;/* 加入到nf_nat_bysource_table */
hlist_add_head_rcu(&nat->bysource,
&nf_nat_bysource[srchash]);
spin_unlock_bh(&nf_nat_lock);
}

/* It's done. */ /* NAT转换完成 */
if (maniptype == NF_NAT_MANIP_DST)
ct->status |= IPS_DST_NAT_DONE;
else
ct->status |= IPS_SRC_NAT_DONE;

return NF_ACCEPT;
}
EXPORT_SYMBOL(nf_nat_setup_info);

重读nat

 

 

unsigned int
nf_nat_setup_info(struct nf_conn *ct,
const struct nf_nat_range *range,
enum nf_nat_manip_type maniptype)
{
struct net *net = nf_ct_net(ct);
struct nf_conntrack_tuple curr_tuple, new_tuple;
struct nf_conn_nat *nat;

/* nat helper or nfctnetlink also setup binding */
nat = nf_ct_nat_ext_add(ct);
if (nat == NULL)
return NF_ACCEPT;
/* 获取是进行DNAT还是SNAT,其中PRE_ROUTING和LOCAL_OUT进行DNAT,LOCAL_IN和POST_ROUTING进行SNAT */
NF_CT_ASSERT(maniptype == NF_NAT_MANIP_SRC ||
maniptype == NF_NAT_MANIP_DST);
BUG_ON(nf_nat_initialized(ct, maniptype));

/* What we've got will look like inverse of reply. Normally
* this is what is in the conntrack, except for prior
* manipulations (future optimization: if num_manips == 0,
* orig_tp = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple)
*/
/* 从应答tuple反向得到当前tuple */
nf_ct_invert_tuplepr(&curr_tuple,
&ct->tuplehash[IP_CT_DIR_REPLY].tuple);

/* 根据当前tuple和range得到NAT转换之后的的tuple */
get_unique_tuple(&new_tuple, &curr_tuple, range, ct, maniptype);


/* NAT转换之后和之前的tuple不同 */
if (!nf_ct_tuple_equal(&new_tuple, &curr_tuple)) {
struct nf_conntrack_tuple reply;

/* Alter conntrack table so will recognize replies. new_tuple为nat后的orig tuple*/
nf_ct_invert_tuplepr(&reply, &new_tuple); /* 通过新tuple得到reply_tuple */
nf_conntrack_alter_reply(ct, &reply);
/* 此时tuple类似如下 */
/*
//内网10.1通过100.1访问200.1,经过SNAT之后得到tuple
tuple SNAT(10.1->200.1, 200.1->100.1)

//外网300.1通过100.1访问20.1,经过DNAT之后,得到tuple
tuple DNAT(300.1->100.1, 20.1->300.1)
*/
/* Non-atomic: we own this at the moment. */
if (maniptype == NF_NAT_MANIP_SRC)
ct->status |= IPS_SRC_NAT;
else
ct->status |= IPS_DST_NAT;

if (nfct_help(ct)) /* 扩展项的调整 比如 alg ftp sip 调整序列号 */
nfct_seqadj_ext_add(ct);
}

if (maniptype == NF_NAT_MANIP_SRC) { /* SNAT */
unsigned int srchash;

srchash = hash_by_src(net,
&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
spin_lock_bh(&nf_nat_lock);
/* nf_conntrack_alter_reply might re-allocate extension aera */
nat = nfct_nat(ct);
nat->ct = ct;/* 加入到nf_nat_bysource_table */
hlist_add_head_rcu(&nat->bysource,
&nf_nat_bysource[srchash]);
spin_unlock_bh(&nf_nat_lock);
}

/* It's done. */ /* NAT转换完成 */
if (maniptype == NF_NAT_MANIP_DST)
ct->status |= IPS_DST_NAT_DONE;
else
ct->status |= IPS_SRC_NAT_DONE;

return NF_ACCEPT;
}
EXPORT_SYMBOL(nf_nat_setup_info);

重读nat

 

 

每个ct在第一个包就会做好snat与dnat, nat的信息全放在reply tuple中,orig tuple不会被改变。一旦第一个包建立好nat信息后,后续再也不会修改tuple内容了。

orig tuple中的地址信息与reply tuple中的地址信息就是原始数据包的信息。例如对A->B数据包同时做snat与dnat,PREROUTING处B被dnat到D,POSTROUTING处A被snat到C。则ct的内容是:  A->B | D->C,  A->B说明了orig方向上数据包刚到达墙时的地址内容,D->C说明reply方向上数据包刚到达墙时的地址内容。

  bysource链中链接了所有CT(做过NAT和未做过NAT),通过ct->nat->bysource,HASH值的计算使用的是CT的orig tuple。其作用是,当为一个新连接做SNAT,需要得到地址映射时,首先对该链进行查找,查找此源IP、协议和端口号是否已经做过了映射。如果做过的话,就需要在SNAT转换时,映射为相同的源IP和端口号。为什么要这么做呢?因为对于UDP来说,有些协议可能会用相同端口和同一主机不同的端口(或不同的主机)进行通信。此时,由于目的地不同,原来已有的映射不可使用,需要一个新的连接。但为了保证通信的的正确性,此时,就要映射为相同的源IP和端口号。其实就是为NAT的打洞服务的。所以bysource就是以源IP、协议和端口号为hash值的一个表,这样在做snat时保证相同的ip+port影射到相同的ip+port。

  第一个包之后,ct的两个方向的tuple内容就固定了,所有的nat操作都必须在第一个包就完成。所以会有daddr = &ct->tuplehash[!dir].tuple.dst.u3;这样的操作。

  对于一个ct,nf_nat_setup_info函数最多只能进入2次,第一次DNAT,第二次SNAT。在nf_nat_follow_master函数中,第一次SNAT,第二次DNAT。

SNAT target的处理函数

static unsigned int ipt_snat_target(struct sk_buff *skb, const struct xt_target_param *par)

{

struct nf_conn *ct;

enum ip_conntrack_info ctinfo;
/*取得用户配置的TARGET参数*/

const struct nf_nat_multi_range_compat *mr = par->targinfo;
/*SNAT target必须配置在POSTROUTING HOOK点上*/

NF_CT_ASSERT(par->hooknum == NF_INET_POST_ROUTING);


/*取得报文携带的conntrack信息及conntrack的状态*/

ct = nf_ct_get(skb, &ctinfo);



/* Connection must be valid and new. */
/*conntrack 的状态必须是以下几种双方未建立连接的状态,否则就报错。

这是因为连接已建立后,报文就直接根据conntrack进行处理NAT即可,

不会再查nat使用SNAT target进行NAT的处理了*/

NF_CT_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED ||

ctinfo == IP_CT_RELATED + IP_CT_IS_REPLY));
/*在进行SNAT前,报文必须先经过路由找到出接口,否则报错*/

NF_CT_ASSERT(par->out != NULL);
/*把用户配置的映射范围传给 nf_nat_setup_info进行进一步处理*/

return nf_nat_setup_info(ct, &mr->range[0], IP_NAT_MANIP_SRC);

}

由于将 ct 挂载在 list上是在nf_conntrack_confirm里面做的,所以 snat后,此时还没有confirm,替换tuple的sip直接替换就行:

/* Since the lookup is lockless, hash insertion must be done after
* starting the timer and setting the CONFIRMED bit. The RCU barriers
* guarantee that no other CPU can find the conntrack before the above
* stores are visible. 将 orig_tuple reply_tuple 添加到 nf_conntrack_hash
*/
__nf_conntrack_hash_insert(ct, hash, reply_hash);

所以:在NAT网关上配置

iptables -t nat -A POSTROUTING  -p udp  -j SNAT --to-source 9.9.9.9

Client 发送 udp报文,报文格式如下

Sip:192.168.3.227 Dip:192.168.5.2 Sport:103 Dprot:105

 进入NAT网关后,在PREROUTING HOOK点先由IP conntrack进行conntrack的建立

重读nat

 

 

建立conntrack后,这时struct nf_conn还不是确认状态。报文经过路由查找后,找到出接口后,走到POSTROUTING HOOK点。

被SNAT注册的hook函数处理,在nat表中找到配置的规则,把conntrack的reply反向信息修改如下

重读nat

 

 

把nf_conn的status的IPS_SRC_NAT_BIT位置1,然后把报文src ip 修改为9.9.9.9。

再由conntrack的 ipv4_confirm来确认conntrack并把该conntrack加入到conntrack hash表中。

然后根据SNAT前查到的路由信息,把报文发送出去。

重读nat

 

 记住:NF_IP_PRI_CONNTRACK_CONFIRM 是最后一个哦!! 在POST_ROUTEING等上其值最大 优先级最小。

{
.hook = ipv4_confirm,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_POST_ROUTING,
.priority = NF_IP_PRI_CONNTRACK_CONFIRM,
},

重读nat

 

 

回应报文,报文格式如下

Sip:192.168.5.2 Sport:105 Dip:9.9.9.9 Dport:103

到达NAT网关后,在PREROUTING HOOK点上,先由conntrack hook处理函数来更新conntrack的连接状态,并把查找到的conntrack 赋值给skb->nfct。

  然后进入NAT的hook函数nf_nat_in,发现报文是reply方向的,并且skb->nfct->status是置位了IPS_SRC_NAT_BIT,就进行DNAT来进行报文的处理。
根据conntrack A的信息来把报文的dip 修改为 192.168.3.227。然后查找路由后发送出去。

  以后每次两个反向的报文进入NAT网关,都会查到建立的conntrack,根据报文的方向以及conntrack中status的信息,
来决定NAT的处理方式,使用该报文反方向的信息来修改报文的IP地址,完成NAT功能

unsigned int nf_nat_packet(struct nf_conn *ct,
enum ip_conntrack_info ctinfo,
unsigned int hooknum,
struct sk_buff *skb)resolve_normal_ct
{
const struct nf_nat_l3proto *l3proto;
const struct nf_nat_l4proto *l4proto;
enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
unsigned long statusbit;
enum nf_nat_manip_type mtype = HOOK2MANIP(hooknum); /* 获取进行SNAT还是DNAT */

if (mtype == NF_NAT_MANIP_SRC)
statusbit = IPS_SRC_NAT;
else
statusbit = IPS_DST_NAT;

/* Invert if this is reply dir. */
if (dir == IP_CT_DIR_REPLY) /* 应答方向需要取反 也就是应答报文 192.168.5.2-->.9.9.9.9-- 取反做dnat 以前做的snat, 这个tuple 其方向是reply */
statusbit ^= IPS_NAT_MASK;

/* Non-atomic: these bits don't change. */
if (ct->status & statusbit) { /* 需要做NAT */
struct nf_conntrack_tuple target;
-------------------------------------------
/* 将ip地址和端口的NAT转换结果写入skb */
if (!l3proto->manip_pkt(skb, 0, l4proto, &target, mtype))
return NF_DROP;nf_nat_ipv4_manip_pkt;
}
return NF_ACCEPT;
}

 

有子连接的NAT实现

有两个关键点:1.主链接能正确的构建出NAT后的expect来识别子连接。2.能够修改主链接数据通道的信息为NAT后的信息。这两点都在动态协议的help中完成,下面我们来看一下它的流程图:

重读nat

 

 

无子连接的NAT

   一个ct用于跟踪一个连接的双方向数据,ct->orig_tuple用于跟踪初始方向数据,ct->reply_tuple用于跟踪应答方向数据。当根据初始方向数据构建ct->orig_tuple时,同时要构建出ct->reply_tuple,用于识别同一连接上应答方向数据。

 如果初始方向的数据在通过防火墙后被做了NAT转换,为识别出NAT数据的应答数据包,则对ct->reply_tuple也要做NAT转换。同时ct上做好相应NAT标记。

因此,上面的信息在初始方向第一个数据包通过后,就要求全部建立好,并且不再改变。

  一个连接上不同方向的数据,都有相对应的tuple(orig_tuple和reply_tuple),所以该连接后续数据都将被识别出来。如果ct上有NAT标记,则根据要去往方向(即另一个方向)的tuple对数据做NAT转换。所以会有ct->tuplehash[!dir].tuple这样的操作。

有子连接的NAT

  子连接是由主连接构建的expect项识别出来的。

   help用于构建expect项,它期待哪个方向的连接,则用那个方向的tuple和数据包中数据通道信息构建expect项。例如期待和当前数据包相反方向的连接,则用相反方向的tuple中的信息(ct->tuplehash[!dir].tuple)。调用help时,NAT转换都已完成(tuple中都包含有正确的识别各自方向的信息),所以这时所使用的信息都是正确和所期望的信息。

 如果子连接还可能有子连接,则构建expect项时,初始化一个helper结构,并赋值给expect->helper指针。

 如果该连接已被做了NAT转换,则对数据包中数据通道信息也要做NAT转换

 

http代理服务器(3-4-7层代理)-网络事件库公共组件、内核kernel驱动 摄像头驱动 tcpip网络协议栈、netfilter、bridge 好像看过!!!! 但行好事 莫问前程 --身高体重180的胖子