ct, info); }
// 返回值不是接受的话直接返回, 数据包将被丢弃 if (ret != NF_ACCEPT) {
WRITE_UNLOCK(&ip_nat_lock); return ret; } } else
DEBUGP(\
maniptype == IP_NAT_MANIP_SRC ? \ ct);
WRITE_UNLOCK(&ip_nat_lock); break; default:
// 连接的NAT信息已经填好, 直接使用 /* ESTABLISHED */
IP_NF_ASSERT(ctinfo == IP_CT_ESTABLISHED
|| ctinfo == (IP_CT_ESTABLISHED+IP_CT_IS_REPLY)); info = &ct->nat.info; }
IP_NF_ASSERT(info);
// 根据NAT info信息对数据包的相应部分进行修改 return do_bindings(ct, ctinfo, info, hooknum, pskb); }
5. do_bindings()函数
do_bindings()是完成具体的NAT操作部分的函数(net/ipv4/netfilter/ip_nat_core.c),修改地址端口等信息,必要时修改数据内容部分信息(这种情况下可能数据包长度会变,序列号/确认号相应会改变,这些都累计进NAT info参数中),并重新各种校验和(TCP/UDP/ICMP校验和,IP头校验和):
/* Do packet manipulations according to binding. */ unsigned int
do_bindings(struct ip_conntrack *ct, enum ip_conntrack_info ctinfo, struct ip_nat_info *info, unsigned int hooknum, struct sk_buff **pskb) {
unsigned int i;
Linux
下nat的实现 6
struct ip_nat_helper *helper; // 数据方向:original or reply
enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); // 是否是TCP协议,TCP协议要处理序列号/确认号 int is_tcp = (*pskb)->nh.iph->protocol == IPPROTO_TCP;
/* Need nat lock to protect against modification, but neither conntrack (referenced) and helper (deleted with synchronize_bh()) can vanish. */ READ_LOCK(&ip_nat_lock);
for (i = 0; i < info->num_manips; i++) {
/* raw socket (tcpdump) may have clone of incoming skb: don't disturb it --RR */ if (skb_cloned(*pskb) && !(*pskb)->sk) {
struct sk_buff *nskb = skb_copy(*pskb, GFP_ATOMIC); if (!nskb) {
READ_UNLOCK(&ip_nat_lock); return NF_DROP; }
kfree_skb(*pskb); *pskb = nskb; }
// 检查数据包方向和hooknum是否是与NAT info中规定的一致 if (info->manips[i].direction == dir
&& info->manips[i].hooknum == hooknum) {
DEBUGP(\ *pskb,
info->manips[i].maniptype == IP_NAT_MANIP_SRC ? \
NIPQUAD(info->manips[i].manip.ip), htons(info->manips[i].manip.u.all));
// 进行具体的NAT操作,修改IP头的地址、TCP、UDP等的端口 manip_pkt((*pskb)->nh.iph->protocol, (*pskb)->nh.iph, (*pskb)->len,
&info->manips[i].manip, info->manips[i].maniptype, &(*pskb)->nfcache); } }
helper = info->helper;
Linux
下nat的实现 7
READ_UNLOCK(&ip_nat_lock);
// 多连接协议 if (helper) {
struct ip_conntrack_expect *exp = NULL; struct list_head *cur_item; int ret = NF_ACCEPT; int helper_called = 0;
DEBUGP(\ /* Always defragged for helpers */
IP_NF_ASSERT(!((*pskb)->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)));
/* Have to grab read lock before sibling_list traversal */ READ_LOCK(&ip_conntrack_lock); // 主连接的子连接链表是倒着搜索的
list_for_each_prev(cur_item, &ct->sibling_list) { // 取得期待的连接信息
exp = list_entry(cur_item, struct ip_conntrack_expect, expected_list);
/* if this expectation is already established, skip */ // 期待的子连接已经到了,不用再处理 if (exp->sibling) continue;
// 检查数据包是否是要修改的数据包,对于UDP、ICMP函数返回始终是1,TCP协议是才可能返回0
if (exp_for_packet(exp, pskb)) {
/* FIXME: May be true multiple times in the * case of UDP!! */
DEBUGP(\// 调用多连接协议的help函数修改内容部分的相关数据 ret = helper->help(ct, exp, info, ctinfo, hooknum, pskb); if (ret != NF_ACCEPT) {
READ_UNLOCK(&ip_conntrack_lock); return ret; }
helper_called = 1; } }
/* Helper might want to manip the packet even when there is no * matching expectation for this packet */
if (!helper_called && helper->flags & IP_NAT_HELPER_F_ALWAYS) { DEBUGP(\
Linux
下nat的实现 8
ret = helper->help(ct, NULL, info, ctinfo, hooknum, pskb); if (ret != NF_ACCEPT) {
READ_UNLOCK(&ip_conntrack_lock); return ret; } }
READ_UNLOCK(&ip_conntrack_lock);
/* Adjust sequence number only once per packet * (helper is called at all hooks) */ // 调整TCP的序列号
if (is_tcp && (hooknum == NF_IP_POST_ROUTING || hooknum == NF_IP_LOCAL_IN)) {
DEBUGP(\ /* future: put this in a l4-proto specific function, * and call this function here. */ ip_nat_seq_adjust(*pskb, ct, ctinfo); }
return ret; } else
return NF_ACCEPT; /* not reached */ }
manip_pkt()函数(net/ipv4/netfilter/ip_nat_core.c)相对就比较简单了,先修改传输层部分的数据参数(如TCP、UDP端口),再修改IP头中的地址:
static void
manip_pkt(u_int16_t proto, struct iphdr *iph, size_t len, const struct ip_conntrack_manip *manip, enum ip_nat_manip_type maniptype, __u32 *nfcache) {
*nfcache |= NFC_ALTERED;
// find_nat_proto函数始终会返回一个协议,因为如果不是能处理的协议,将 // 返回缺省的未知协议处理,由此也可知在IP上层协议NAT处理结构中的 // manip_pkt()函数不能为空,这个函数可以什么都不作,但不能为NULL find_nat_proto(proto)->manip_pkt(iph, len, manip, maniptype);
// 根据NAT类型,修改源或目的IP地址 if (maniptype == IP_NAT_MANIP_SRC) {
iph->check = ip_nat_cheat_check(~iph->saddr, manip->ip,
Linux
下nat的实现 9
iph->check);
iph->saddr = manip->ip; } else {
iph->check = ip_nat_cheat_check(~iph->daddr, manip->ip, iph->check);
iph->daddr = manip->ip; } #if 0
if (ip_fast_csum((u8 *)iph, iph->ihl) != 0) DEBUGP(\ if (proto == IPPROTO_TCP) {
void *th = (u_int32_t *)iph + iph->ihl;
if (tcp_v4_check(th, len - 4*iph->ihl, iph->saddr, iph->daddr, csum_partial((char *)th, len-4*iph->ihl, 0))) DEBUGP(\ } #endif }
6. SNAT、DNAT目标函数
前面在ip_nat_fn()函数中调用的ip_nat_rule_find()用来查找NAT规则,执行规则的动作,规则目标不是SNAT就是DNAT,该目标的具体实现在net/ipv4/netfilter/ip_nat_rule.c中。不论是SNAT还是DNAT规则,其目标函数最终都是调用ip_nat_setup_info()函数来建立连接的NAT info信息。
net/ipv4/netfilter/ip_nat_rule.c:
/* Source NAT */
static unsigned int ipt_snat_target(struct sk_buff **pskb, unsigned int hooknum, const struct net_device *in, const struct net_device *out, const void *targinfo, void *userinfo) {
struct ip_conntrack *ct;
enum ip_conntrack_info ctinfo;
IP_NF_ASSERT(hooknum == NF_IP_POST_ROUTING); ct = ip_conntrack_get(*pskb, &ctinfo); /* Connection must be valid and new. */
IP_NF_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED)); IP_NF_ASSERT(out);
Linux
下nat的实现 10