- 论坛徽章:
- 0
|
ip_nat_out
- static unsigned int
- ip_nat_out(unsigned int hooknum,
- struct sk_buff **pskb,
- const struct net_device *in,
- const struct net_device *out,
- int (*okfn)(struct sk_buff *))
- {
- /* root is playing with raw sockets. */
- if ((*pskb)->len < sizeof(struct iphdr)
- || (*pskb)->nh.iph->ihl * 4 < sizeof(struct iphdr))
- return NF_ACCEPT;
- if ((*pskb)->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) {
- *pskb = ip_ct_gather_frags(*pskb, IP_DEFRAG_NAT_OUT);
- if (!*pskb)
- return NF_STOLEN;
- }
- return ip_nat_fn(hooknum, pskb, in, out, okfn);
- }
复制代码
在进行了IP包的长度较验和分片检查之后,函数进入ip_nat_fn,它是整个地址转换的核心函数之一:
- static unsigned int
- ip_nat_fn(unsigned int hooknum,
- struct sk_buff **pskb,
- const struct net_device *in,
- const struct net_device *out,
- int (*okfn)(struct sk_buff *))
- {
- struct ip_conntrack *ct;
- enum ip_conntrack_info ctinfo;
- struct ip_nat_info *info;
- /* maniptype通过调用HOOK2MAINIP宏判断Hook点,指明转换类型是源地址转换还是目的地址转换,为0(IP_NAT_MANIP_SRC)表示源地址转换,为1(IP_NAT_MANIP_DST)表示目的地址转换 */
- enum ip_nat_manip_type maniptype = HOOK2MANIP(hooknum);
- /* 前面函数中已经处理过分片的情况,这里应该不会再出现分片包了. */
- IP_NF_ASSERT(!((*pskb)->nh.iph->frag_off
- & htons(IP_MF|IP_OFFSET)));
- /*因为地址转换会修改数据包,所以这里先初始化将其设置为“未修改”标志,后面进行数据包修改时再来重置这个标志*/
- (*pskb)->nfcache |= NFC_UNKNOWN;
- /* If we had a hardware checksum before, it's now invalid */
- if ((*pskb)->ip_summed == CHECKSUM_HW)
- if (skb_checksum_help(*pskb, (out == NULL)))
- return NF_DROP;
-
- /*取得数据包的连接状态*/
- ct = ip_conntrack_get(*pskb, &ctinfo);
- /* 如果找不到对应连接,则应该直接放行它,而不再对其进行转换处理,特别地,ICMP重定向报文将会被丢弃*/
- if (!ct) {
- /* Exception: ICMP redirect to new connection (not in
- hash table yet). We must not let this through, in
- case we're doing NAT to the same network. */
- if ((*pskb)->nh.iph->protocol == IPPROTO_ICMP) {
- struct icmphdr _hdr, *hp;
- hp = skb_header_pointer(*pskb,
- (*pskb)->nh.iph->ihl*4,
- sizeof(_hdr), &_hdr);
- if (hp != NULL &&
- hp->type == ICMP_REDIRECT)
- return NF_DROP;
- }
- return NF_ACCEPT;
- }
- /*判断连接状态,调用相应的处理函数*/
- switch (ctinfo) {
- case IP_CT_RELATED:
- case IP_CT_RELATED+IP_CT_IS_REPLY:
- if ((*pskb)->nh.iph->protocol == IPPROTO_ICMP) {
- if (!icmp_reply_translation(pskb, ct, maniptype,
- CTINFO2DIR(ctinfo)))
- return NF_DROP;
- else
- return NF_ACCEPT;
- }
- /* Fall thru... (Only ICMPs can be IP_CT_IS_REPLY) */
- case IP_CT_NEW:
- info = &ct->nat.info;
- /* 观察这个新建封包是否已经被NAT模块修改过了,如果没有,进一步调用ip_nat_rule_find函数*/
- if (!ip_nat_initialized(ct, maniptype)) {
- unsigned int ret;
- /* LOCAL_IN hook doesn't have a chain! */
- if (hooknum == NF_IP_LOCAL_IN)
- ret = alloc_null_binding(ct, info, hooknum);
- else
- ret = ip_nat_rule_find(pskb, hooknum,
- in, out, ct,
- info);
- if (ret != NF_ACCEPT) {
- return ret;
- }
- } else
- DEBUGP("Already setup manip %s for ct %p\n",
- maniptype == IP_NAT_MANIP_SRC ? "SRC" : "DST",
- ct);
- break;
- default:
- /* ESTABLISHED */
- IP_NF_ASSERT(ctinfo == IP_CT_ESTABLISHED
- || ctinfo == (IP_CT_ESTABLISHED+IP_CT_IS_REPLY));
- info = &ct->nat.info;
- }
- IP_NF_ASSERT(info);
- return nat_packet(ct, ctinfo, hooknum, pskb);
- }
复制代码
我们假设这是一个刚刚进入Linux的数据包,它是一个新建状态的连接,首先调用ip_nat_initialized函数判断它是否已经被地址转换例程修改过,即是否已经设置了相应转换类型的标志位:
static inline int ip_nat_initialized(struct ip_conntrack *conntrack,
enum ip_nat_manip_type manip)
{
/*如果是源地址转换,即测试源地址转换位,否则,测试目的地址转换位*/
if (manip == IP_NAT_MANIP_SRC)
return test_bit(IPS_SRC_NAT_DONE_BIT, &conntrack->status);
return test_bit(IPS_DST_NAT_DONE_BIT, &conntrack->status);
}
对于一个中转没有被修改过的包,ip_nat_rule_find函数将会被调用,以作进一步处理。
ip_nat_rule_find
ip_nat_rule_find 函数,从名称上我们就可以看出,它的含义是“NAT 规则查找”,它是一个规则匹配函数,其最重要的工作,就是调用ipt_do_table函数进行规则的检测,这个函数我们在包过滤一章已经对它进行了详细的分析,当ipt_do_table函数发现数据包匹配一条源地址转换的规则时,则会调用SNAT模块的target函数。
- int ip_nat_rule_find(struct sk_buff **pskb,
- unsigned int hooknum,
- const struct net_device *in,
- const struct net_device *out,
- struct ip_conntrack *ct,
- struct ip_nat_info *info)
- {
- int ret;
- /*NAT规则匹配*/
- ret = ipt_do_table(pskb, hooknum, in, out, &nat_table, NULL);
- if (ret == NF_ACCEPT) {
- if (!ip_nat_initialized(ct, HOOK2MANIP(hooknum)))
- /* NUL mapping */
- ret = alloc_null_binding(ct, info, hooknum);
- }
- return ret;
- }
复制代码
前面我们已经讨论过,SNAT注册的的target函数是ipt_snat_target,这个函数,首先取得规则中“转换后地址”的信息,然后将工作交给ip_nat_setup_info进一步处理:
- /* Source NAT */
- static unsigned int ipt_snat_target(struct sk_buff **pskb,
- const struct net_device *in,
- const struct net_device *out,
- unsigned int hooknum,
- const void *targinfo,
- void *userinfo)
- {
- struct ip_conntrack *ct;
- enum ip_conntrack_info ctinfo;
- /*取得规则中的target部份*/
- const struct ip_nat_multi_range_compat *mr = targinfo;
- 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
- || ctinfo == IP_CT_RELATED + IP_CT_IS_REPLY));
- IP_NF_ASSERT(out);
- return ip_nat_setup_info(ct, &mr->range[0], hooknum);
- }
复制代码
ip_nat_setup_info
ip_nat_setup_info 函数是地址转换中又一个非常重要的函数:
- unsigned int
- ip_nat_setup_info(struct ip_conntrack *conntrack, /*数据包对应的连接*/
- const struct ip_nat_range range, /*转换后的地址池*/
- unsigned int hooknum) /*Hook点*/
- {
- struct ip_conntrack_tuple curr_tuple, new_tuple;
- struct ip_nat_info *info = &conntrack->nat.info;
- int have_to_hash = !(conntrack->status & IPS_NAT_DONE_MASK);
- enum ip_nat_manip_type maniptype = HOOK2MANIP(hooknum);
- IP_NF_ASSERT(hooknum == NF_IP_PRE_ROUTING
- || hooknum == NF_IP_POST_ROUTING
- || hooknum == NF_IP_LOCAL_IN
- || hooknum == NF_IP_LOCAL_OUT);
- BUG_ON(ip_nat_initialized(conntrack, 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 =
- conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple) */
- invert_tuplepr(&curr_tuple,
- &conntrack->tuplehash[IP_CT_DIR_REPLY].tuple);
- get_unique_tuple(&new_tuple, &curr_tuple, range, conntrack, maniptype);
- if (!ip_ct_tuple_equal(&new_tuple, &curr_tuple)) {
- struct ip_conntrack_tuple reply;
- /* Alter conntrack table so will recognize replies. */
- invert_tuplepr(&reply, &new_tuple);
- ip_conntrack_alter_reply(conntrack, &reply);
- /* Non-atomic: we own this at the moment. */
- if (maniptype == IP_NAT_MANIP_SRC)
- conntrack->status |= IPS_SRC_NAT;
- else
- conntrack->status |= IPS_DST_NAT;
- }
- /* Place in source hash if this is the first time. */
- if (have_to_hash) {
- unsigned int srchash
- = hash_by_src(&conntrack->tuplehash[IP_CT_DIR_ORIGINAL]
- .tuple);
- WRITE_LOCK(&ip_nat_lock);
- list_add(&info->bysource, &bysource[srchash]);
- WRITE_UNLOCK(&ip_nat_lock);
- }
- /* It's done. */
- if (maniptype == IP_NAT_MANIP_DST)
- set_bit(IPS_DST_NAT_DONE_BIT, &conntrack->status);
- else
- set_bit(IPS_SRC_NAT_DONE_BIT, &conntrack->status);
- return NF_ACCEPT;
- }
复制代码
这个函数转换来转换去,让人头大,慢慢抽丝拨茧先。首先调用invert_tuplepr取得一个当前数据包对应的replay tuple,然后对其取反得到一个curr_tuple,接着调用get_unique_tuple函数:
- static void
- get_unique_tuple(struct ip_conntrack_tuple *tuple,
- const struct ip_conntrack_tuple *orig_tuple,
- const struct ip_nat_range *range,
- struct ip_conntrack *conntrack,
- enum ip_nat_manip_type maniptype)
- {
- struct ip_nat_protocol *proto
- = ip_nat_find_proto(orig_tuple->dst.protonum);
- /* 1) If this srcip/proto/src-proto-part is currently mapped,
- and that same mapping gives a unique tuple within the given
- range, use that.
- This is only required for source (ie. NAT/masq) mappings.
- So far, we don't do local source mappings, so multiple
- manips not an issue. */
- if (maniptype == IP_NAT_MANIP_SRC) {
- if (find_appropriate_src(orig_tuple, tuple, range)) {
- DEBUGP("get_unique_tuple: Found current src map\n");
- if (!ip_nat_used_tuple(tuple, conntrack))
- return;
- }
- }
- /* 2) Select the least-used IP/proto combination in the given
- range. */
- *tuple = *orig_tuple;
- find_best_ips_proto(tuple, range, conntrack, maniptype);
- /* 3) The per-protocol part of the manip is made to map into
- the range to make a unique tuple. */
- /* Only bother mapping if it's not already in range and unique */
- if ((!(range->flags & IP_NAT_RANGE_PROTO_SPECIFIED)
- || proto->in_range(tuple, maniptype, &range->min, &range->max))
- && !ip_nat_used_tuple(tuple, conntrack))
- return;
- /* Last change: get protocol to try to obtain unique tuple. */
- proto->unique_tuple(tuple, range, maniptype, conntrack);
- }
复制代码 |
|