红联Linux门户
Linux帮助

iptables的端口范围映射

发布时间:2017-05-03 10:51:54来源:linux网站作者:普朗克常量
使用iptables来进行端口映射我们似乎比较熟悉,指定一个范围的端口映射好像也略懂一二,但实际测试结果却不尽人意。
 
单个端口映射
一般而言要在路由器里实现一条端口映射规则,需要两个iptables规则,一条是目的地址转换一条是源地址转换。 如下(192.168.40.200是wan口地址,10.0.0.150是LAN侧主机地址):
iptables -t nat -I PREROUTING -p tcp -d 192.168.40.200 --dport 23 -j DNAT --to 10.0.0.150:23
iptables -t nat -I POSTROUTING -p tcp -s 10.0.0.150 --sport 23 -j SNAT --to 192.168.40.200:23
 
范围端口映射
查看man iptables可知,范围端口映射规则也挺简单,匹配源时写为--sport port[:port],target时写为[ipaddr][-ipaddr][:port[-port]]。即上面单个的端口映射规则改为这种形式:
iptables -t nat -I PREROUTING -p tcp -d 192.168.40.200 --dport 23:100 -j DNAT --to 10.0.0.150:23-100
重点不在于规则怎么写,而是范围端口映射时是怎么映射?因为它可以少对多也可以多对少。例如配置[10-12]映射到[100-102],我怎么知道端口11会映射到具体那个端口,101吗? [100-200]映射到[10-20]时又会怎么样呢?
如果测试一下第一个问题会发现总是映射到100端口,不是想象的101端口。
 
代码跟踪
内核代码一向难度较高,这里也只是浅浅的忽悠一下。
跟踪一下DNAT target的动作,DNAT动作的代码在net/ipv4/netfilter/nf_nat_rule.c里的ipt_dnat_target()函数,然后进入nf_nat_setup_info() ==》 get_unique_tuple()。
get_unique_tuple函数
1.static void
2.get_unique_tuple(struct nf_conntrack_tuple *tuple,
3.         const struct nf_conntrack_tuple *orig_tuple,
4.         const struct nf_nat_range *range,
5.         struct nf_conn *ct,
6.         enum nf_nat_manip_type maniptype)
7.{
8.    struct net *net = nf_ct_net(ct);
9.    const struct nf_nat_protocol *proto;
10.    u16 zone = nf_ct_zone(ct);
11.
12.    /* 1) If this srcip/proto/src-proto-part is currently mapped,
13.       and that same mapping gives a unique tuple within the given
14.       range, use that.
15.
16.       This is only required for source (ie. NAT/masq) mappings.
17.       So far, we don't do local source mappings, so multiple
18.       manips not an issue.  */
19.    if (maniptype == IP_NAT_MANIP_SRC &&
20.        !(range->flags & IP_NAT_RANGE_PROTO_RANDOM)) {
21.        if (find_appropriate_src(net, zone, orig_tuple, tuple, range)) {
22.            pr_debug("get_unique_tuple: Found current src map\n");
23.            if (!nf_nat_used_tuple(tuple, ct))
24.                return;
25.        }
26.    }
27.    printk("%s(%d)\n", __FUNCTION__, __LINE__);
28.    /* 2) Select the least-used IP/proto combination in the given
29.       range. */
30.    *tuple = *orig_tuple;
31.    find_best_ips_proto(zone, tuple, range, ct, maniptype);
32.
33.    /* 3) The per-protocol part of the manip is made to map into
34.       the range to make a unique tuple. */
35.
36.    rcu_read_lock();
37.    proto = __nf_nat_proto_find(orig_tuple->dst.protonum);
38.
39.    /* Only bother mapping if it's not already in range and unique */
40.    if (!(range->flags & IP_NAT_RANGE_PROTO_RANDOM) &&
41.        (!(range->flags & IP_NAT_RANGE_PROTO_SPECIFIED) ||
42.         proto->in_range(tuple, maniptype, &range->min, &range->max)) &&
43.        !nf_nat_used_tuple(tuple, ct))
44.        goto out;
45.
46.    /* Last change: get protocol to try to obtain unique tuple. */
47.    proto->unique_tuple(tuple, range, maniptype, ct);
48.out:
49.    printk("%s(%d)\n", __FUNCTION__, __LINE__);
50.    rcu_read_unlock();
51.}
52.
重点在第40行的判断,里面的proto->in_range(tuple, maniptype, &range->min, &range->max)会判断原tuple的端口是否在映射的端口范围内,如果在那就基本要直接goto out了。否则会进入proto->unique_tuple ==> tcp_unique_tuple ==>nf_nat_proto_unique_tuple。
nf_nat_proto_unique_tuple函数,看这一句就好了。
1.for (i = 0; ; ++off) {
2.        *portptr = htons(min + off % range_size);
3.        if (++i != range_size && nf_nat_used_tuple(tuple, ct))
4.            continue;
5.        if (!(range->flags & IP_NAT_RANGE_PROTO_RANDOM))
6.            *rover = off;
7.        return;
8.    }
portptr为传出的参数,就是映射到这个端口啦。min为映射端口范围的最小值,off为偏移,大多数时候为0,range_size为端口范围大小。所以进到这里一般情况下,就会映射范围端口里面最小的端口。
 
结论
当匹配的端口在映射端口的区间内时,那么端口号不会被修改。如果匹配端口不在映射端口的区间内,则大多数情况下映射端口号为最小的端口号,即映射端口号是不可预测的。
举例: [1000-2000] 映射到[1000-2000]时会一一映射。
[1000-2000] 映射到[3000-4000]结果不可预知,一般会映射到3000端口。
 
本文永久更新地址:http://www.linuxdiyf.com/linux/30482.html