Linux下两种 DNAT 用法的差异

前段时间使用 iptablesDNAT 实现一个业务需求的时候,遇到了一些问题这里将其整个过程记录下来。

需求

这里假设开发机地址为 192.168.3.80,要实现的需求是当用户在开发机访问一个IP地址 192.168.3.196时,将请求转发到另一台机器 192.168.3.58,很明显直接使用 DNAT 来实现即可。

问题现象

iptables 命令如下

sudo iptables -t nat -F
sudo iptables -t nat -A PREROUTING -d 192.168.3.196 -p tcp --dport 8080 -j DNAT --to-destination 192.168.3.58:8080
sudo iptables -t nat -A POSTROUTING -d 192.168.3.58 -p tcp --dport 8080 -j SNAT --to-source 192.168.3.196:8080

这时在开发机器访问

curl http://192.168.3.196:8080

发现提示错误

curl: (7) Failed to connect to 192.168.3.196 port 8080: Connection refused

奇怪了,竟然不能访问,确认路由规则是写入成功的。网上查找了一些资料好像全是这种写法,只不过用法有怕差异,这时直觉告诉我应该对 DNAT 理解不到位,遗漏了一些重要的知识点。

上面这种写法一般都是将开发机当作一个中转服务器跳板来使用,多种情况下都有一个公网ip,与我们的真正需求有一些不一样。

现在我们再以将其视为中转服务器的角色测试一次,当然这个规则不能直接使用上面的这个,需要把访问的目标ip更换成开发机器的IP地址。

sudo iptables -t nat -F
sudo iptables -t nat -A PREROUTING -d 192.168.3.80 -p tcp --dport 8080 -j DNAT --to-destination 192.168.3.58:8080
sudo iptables -t nat -A POSTROUTING -d 192.168.3.58 -p tcp --dport 8080 -j SNAT --to-source 192.168.3.80:8080

第一条是数据包出去规则,需要做DNAT,第二条是数据包回来规则,必须做一次 SNAT,否则数据包将去无回,无法响应。

这时再找一台机器访问 curl 192.168.3.80:8080,可以看到响应结果符合预期。

问题分析

现在我们基本确认了是我们的用法不对,到底是哪里出错了呢?这里我们一起看一下这张 iptables 数据流向图。

iptables Processing Flowchart

从图中可以看到,对于数据流入一共有两类,一类是外部数据包流入 ,即左侧的 Incoming Packet;另一类是本机生成的数据包流入,即右侧的 Locally generated Packet,对于对数据包的流出只有一处,即下方的 Outgoing Packet

对于数据包首个经过的表是不一样的,对于外部流入的数据包首个经过的是PREROUTING 表,而对于本地生成的数据包而言经过的是 OUTPUT 这个表,最后统一从同一个地方流出。

也就是说针对不的类型的包,经过的表是不同的,这个正是我们最上面失败的原因。

解决问题

我们要实现的场景其实是 Locally generated Packet 这类,所以使用的表应该是 OUTPUT才是正确的,现在我们清除原来的规则,重新写入新规则测试一下

sudo iptables -t nat -F
sudo iptables -t nat -A OUTPUT -d 192.168.3.196 -p tcp --dport 8080 -j DNAT --to-destination 192.168.3.58:8080

注意对于 SNAT而言,只对 INPUT/POSTROUTING有效。

再次测试 curl 192.168.3.196:8080 响应正常。

总结

针对 iptables 的 DNAT 的实现,需要根据数据包的来源不同而采用不同的处理方法,一共分外部数据包和本地数据包两类。其中对于外部数据包除了做 DNAT外,还要再做一个 SNAT 规则,否则数据包将有去无回;而对于本地数据包而言,只需要在 OUTPUT 表中做一个 DNAT 即可,并不需要SNAT,同时也不支持 SNAT。对于SNAT 只对 INPUT/POSTROUTEING 才有效。

Linux中调试 iptables

环境:

客户端(windows) 192.168.6.21

服务器(Ubuntu): 192.168.6.23

开启iptables调试内核模块

 $ modprobe nf_log_ipv4
 $ sysctl net.netfilter.nf_log.2
 net.netfilter.nf_log.2 = nf_log_ipv4

添加iptables规则

 $ iptables -t raw -A PREROUTING -p icmp -j TRACE
 $ iptables -t raw -A OUTPUT -p icmp -j TRACE

测试规则

客户端执行 ping 命令,

 $ ping 192.168.6.23 -n 1

这里使用 -n 参数指定发送的包数量为1,方便我们分析日志

此时在服务器上执行查看日志命令, 日志文件为:/var/log/syslog 或者 /var/log/kern.log 或者 /var/log/messages

$ tail -f /var/log/syslog
 Jul 20 11:28:40 ubuntu kernel: [ 7606.531051] TRACE: raw:PREROUTING:policy:2 IN=ens37 OUT= MAC=00:0c:29:30:06:44:00:68:eb:c6:60:f2:08:00 SRC=192.168.6.21 DST=192.168.6.23 LEN=60 TOS=0x00 PREC=0x00 TTL=128 ID=33555 PROTO=ICMP TYPE=8 CODE=0 ID=1 SEQ=608 
 Jul 20 11:28:40 ubuntu kernel: [ 7606.531146] TRACE: nat:PREROUTING:rule:1 IN=ens37 OUT= MAC=00:0c:29:30:06:44:00:68:eb:c6:60:f2:08:00 SRC=192.168.6.21 DST=192.168.6.23 LEN=60 TOS=0x00 PREC=0x00 TTL=128 ID=33555 PROTO=ICMP TYPE=8 CODE=0 ID=1 SEQ=608 
 Jul 20 11:28:40 ubuntu kernel: [ 7606.531192] TRACE: nat:DOCKER:return:3 IN=ens37 OUT= MAC=00:0c:29:30:06:44:00:68:eb:c6:60:f2:08:00 SRC=192.168.6.21 DST=192.168.6.23 LEN=60 TOS=0x00 PREC=0x00 TTL=128 ID=33555 PROTO=ICMP TYPE=8 CODE=0 ID=1 SEQ=608 
 Jul 20 11:28:40 ubuntu kernel: [ 7606.531259] TRACE: nat:PREROUTING:policy:2 IN=ens37 OUT= MAC=00:0c:29:30:06:44:00:68:eb:c6:60:f2:08:00 SRC=192.168.6.21 DST=192.168.6.23 LEN=60 TOS=0x00 PREC=0x00 TTL=128 ID=33555 PROTO=ICMP TYPE=8 CODE=0 ID=1 SEQ=608 
 Jul 20 11:28:40 ubuntu kernel: [ 7606.531316] TRACE: filter:INPUT:policy:1 IN=ens37 OUT= MAC=00:0c:29:30:06:44:00:68:eb:c6:60:f2:08:00 SRC=192.168.6.21 DST=192.168.6.23 LEN=60 TOS=0x00 PREC=0x00 TTL=128 ID=33555 PROTO=ICMP TYPE=8 CODE=0 ID=1 SEQ=608 
 Jul 20 11:28:40 ubuntu kernel: [ 7606.531373] TRACE: nat:INPUT:policy:1 IN=ens37 OUT= MAC=00:0c:29:30:06:44:00:68:eb:c6:60:f2:08:00 SRC=192.168.6.21 DST=192.168.6.23 LEN=60 TOS=0x00 PREC=0x00 TTL=128 ID=33555 PROTO=ICMP TYPE=8 CODE=0 ID=1 SEQ=608 
 Jul 20 11:28:40 ubuntu kernel: [ 7606.531424] TRACE: raw:OUTPUT:policy:2 IN= OUT=ens37 SRC=192.168.6.23 DST=192.168.6.21 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=35888 PROTO=ICMP TYPE=0 CODE=0 ID=1 SEQ=608 
 Jul 20 11:28:40 ubuntu kernel: [ 7606.531488] TRACE: filter:OUTPUT:policy:1 IN= OUT=ens37 SRC=192.168.6.23 DST=192.168.6.21 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=35888 PROTO=ICMP TYPE=0 CODE=0 ID=1 SEQ=608

可以看到除了流量来源 SRC 和目的 DST, 还有一些 ICMP 协议相关的字段,如 TYPE, CODE; 对于 ICMP协议 TYPE 有多类值,其中CODE 根据 TYPE 值的不同而不同。

日志字段

Jul 20 11:28:40 ubuntu kernel: [ 7606.531051] TRACE: raw:PREROUTING:policy:2 IN=ens37 OUT= MAC=00:0c:29:30:06:44:00:68:eb:c6:60:f2:08:00 SRC=192.168.6.21 DST=192.168.6.23 LEN=60 TOS=0x00 PREC=0x00 TTL=128 ID=33555 PROTO=ICMP TYPE=8 CODE=0 ID=1 SEQ=608 

raw:PREROUTING:policy:2 这里以”:”分隔四类字段值,分别为 raw 表:PREROUTING 键:policy或 rule : 编号

IN 流量流入网卡名称,当流量 为流出时,此字段为空

OUT 流量流出网卡名称,当流量为流入时,此字段为空

MAC 网卡MAC地址

SRC 流量来源IP

DST 流量目的IP

LEN 数据包大小

TOS 服务类型

ID 流唯一标识, 如日志中请求ID为 33555, 响应ID为 35888

PROTO 数据流协议

TYPE 协议ICMP的类型,见下表

CODE 协议ICMP类型对应的code

上面日志中第2-7条记录为ping 请求(TYPE=8 CODE=0),而最后两条记录为对ping命令的响应(TYPE=0 CODE=0),由于ping 请求经过了nat 表(PREROUTING)和 filter 两个表的不同链,所有打印多条记录。

ICMP类型

TYPECODEDescriptionQueryError
00Echo Reply——回显应答(Ping应答)x
30Network Unreachable——网络不可达x
31Host Unreachable——主机不可达x
32Protocol Unreachable——协议不可达x
33Port Unreachable——端口不可达x
34Fragmentation needed but no frag. bit set——需要进行分片但设置不分片比特x
35Source routing failed——源站选路失败x
36Destination network unknown——目的网络未知x
37Destination host unknown——目的主机未知x
38Source host isolated (obsolete)——源主机被隔离(作废不用)x
39Destination network administratively prohibited——目的网络被强制禁止x
310Destination host administratively prohibited——目的主机被强制禁止x
311Network unreachable for TOS——由于服务类型TOS,网络不可达x
312Host unreachable for TOS——由于服务类型TOS,主机不可达x
313Communication administratively prohibited by filtering——由于过滤,通信被强制禁止x
314Host precedence violation——主机越权x
315Precedence cutoff in effect——优先中止生效x
40Source quench——源端被关闭(基本流控制)
50Redirect for network——对网络重定向
51Redirect for host——对主机重定向
52Redirect for TOS and network——对服务类型和网络重定向
53Redirect for TOS and host——对服务类型和主机重定向
80Echo request——回显请求(Ping请求)x
90Router advertisement——路由器通告
100Route solicitation——路由器请求
110TTL equals 0 during transit——传输期间生存时间为0x
111TTL equals 0 during reassembly——在数据报组装期间生存时间为0x
120IP header bad (catchall error)——坏的IP首部(包括各种差错)x
121Required options missing——缺少必需的选项x
130Timestamp request (obsolete)——时间戳请求(作废不用)x
14Timestamp reply (obsolete)——时间戳应答(作废不用)x
150Information request (obsolete)——信息请求(作废不用)x
160Information reply (obsolete)——信息应答(作废不用)x
170Address mask request——地址掩码请求x
180Address mask reply——地址掩码应答x

在日志里同时还有 raw表的 PREROUTING 和 OUTPUT 链的相关记录。

现在我们再添加一条禁止ICMP的规则,这里即可以在filter 表中的 INPUT 链中添加,也可以在 OUTPUT 链中添加。

 $ iptables -t filter -A OUTPUT -d 192.168.6.21 -j DROP

这里我们添加在了 OUTPUT 链里,所以这里使用的 -d 参数值为 192.168.6.21

现在我们再看一下日志输出

Jul 20 11:09:58 ubuntu kernel: [ 6484.565458] TRACE: raw:PREROUTING:policy:2 IN=ens37 OUT= MAC=00:0c:29:30:06:44:00:68:eb:c6:60:f2:08:00 SRC=192.168.6.21 DST=192.168.6.23 LEN=60 TOS=0x00 PREC=0x00 TTL=128 ID=33554 PROTO=ICMP TYPE=8 CODE=0 ID=1 SEQ=582 
 Jul 20 11:09:58 ubuntu kernel: [ 6484.565548] TRACE: nat:PREROUTING:rule:1 IN=ens37 OUT= MAC=00:0c:29:30:06:44:00:68:eb:c6:60:f2:08:00 SRC=192.168.6.21 DST=192.168.6.23 LEN=60 TOS=0x00 PREC=0x00 TTL=128 ID=33554 PROTO=ICMP TYPE=8 CODE=0 ID=1 SEQ=582 
 Jul 20 11:09:58 ubuntu kernel: [ 6484.565592] TRACE: nat:DOCKER:return:3 IN=ens37 OUT= MAC=00:0c:29:30:06:44:00:68:eb:c6:60:f2:08:00 SRC=192.168.6.21 DST=192.168.6.23 LEN=60 TOS=0x00 PREC=0x00 TTL=128 ID=33554 PROTO=ICMP TYPE=8 CODE=0 ID=1 SEQ=582 
 Jul 20 11:09:58 ubuntu kernel: [ 6484.565631] TRACE: nat:PREROUTING:policy:2 IN=ens37 OUT= MAC=00:0c:29:30:06:44:00:68:eb:c6:60:f2:08:00 SRC=192.168.6.21 DST=192.168.6.23 LEN=60 TOS=0x00 PREC=0x00 TTL=128 ID=33554 PROTO=ICMP TYPE=8 CODE=0 ID=1 SEQ=582 
 Jul 20 11:09:58 ubuntu kernel: [ 6484.565673] TRACE: filter:INPUT:policy:1 IN=ens37 OUT= MAC=00:0c:29:30:06:44:00:68:eb:c6:60:f2:08:00 SRC=192.168.6.21 DST=192.168.6.23 LEN=60 TOS=0x00 PREC=0x00 TTL=128 ID=33554 PROTO=ICMP TYPE=8 CODE=0 ID=1 SEQ=582 
 Jul 20 11:09:58 ubuntu kernel: [ 6484.565713] TRACE: nat:INPUT:policy:1 IN=ens37 OUT= MAC=00:0c:29:30:06:44:00:68:eb:c6:60:f2:08:00 SRC=192.168.6.21 DST=192.168.6.23 LEN=60 TOS=0x00 PREC=0x00 TTL=128 ID=33554 PROTO=ICMP TYPE=8 CODE=0 ID=1 SEQ=582 
 Jul 20 11:09:58 ubuntu kernel: [ 6484.565763] TRACE: raw:OUTPUT:policy:2 IN= OUT=ens37 SRC=192.168.6.23 DST=192.168.6.21 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=14584 PROTO=ICMP TYPE=0 CODE=0 ID=1 SEQ=582 
 Jul 20 11:09:58 ubuntu kernel: [ 6484.565804] TRACE: filter:OUTPUT:rule:1 IN= OUT=ens37 SRC=192.168.6.23 DST=192.168.6.21 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=14584 PROTO=ICMP TYPE=0 CODE=0 ID=1 SEQ=582

请求ID为 33554,TYPE=8, 而响应ID为 14584,TYPE=0

我们看下最后一条日志,其中filter:OUTPUT:rule:1 表示路径为 filter 表的 OUTPUT 链中的编号为1的规则,这条应该是我们上面添加的规则 ,我们现在确认一下

 iptables -t filter -L OUTPUT -nv --line-number 
 Chain OUTPUT (policy ACCEPT 65 packets, 7466 bytes)
 num   pkts bytes target     prot opt in     out     source               destination        
 1       3   180 DROP       all -- *     *       0.0.0.0/0           192.168.6.21

这里是添加在了OUTPUT链中,所以 dst 就是客户端的ip地址,src 就是服务器的地址,这个与我们在 INPUT 链中的正好相反。

清理现场

$ modprobe -r nf_log_ipv4
modprobe: FATAL: Module nf_log_syslog is in use.

参考资料

iptables规则的查看、添加、删除和修改[教程]

在 Linux 中 iptables 实际上只是一个操作 Linux 内核 Netfilter 子系统的“界面”。顾名思义,Netfilter 子系统的作用,就是 Linux 内核里挡在“网卡”和“用户态进程”之间的一道“防火墙”。 也就是说 iptables 工作在用户态,它和我们平时开发的应用程序完全一样的,只是它的作用是用来操作 NetFilter 的一个工具。而 NetFilter 工作在内核态,它们的关系,可以用如下的示意图来表示:

图来自极客时间

在 iptables 中存在四表五链的概念。

表分别为 filternatrawmangle ,当数据包抵达防火墙时,将依次应用 raw、mangle、nat、和 filter 表中对应链内的规则,其中表的应用顺序为:raw -> mangle -> nat -> filter,而表中链的规则自上向下依次执行,执行中有可能跳转到其它链中继续执行。

iptables Processing Flowchart

如果按七层网络协议的话,则 ipables 中的数据流向为

(来自Netfilter 官方的原理图)Packet flow in Netfilter and General Networking

其中每一层又分为”INPUT PATH“、“FORWARD PATH” 和 “OUTPUT PATH” 三种。对于”INPUT PATH” 和 “OUTPUT PATH“包含四个表,而对于” FORWARD PATH“而言只包含两个表。

如果想查看指定类型的表通过 -t 参数指定,如

$ iptables -t nat -L

可以显示所有 nat 表的规则,如果不指定 -t 参数,则默认只显示 filter 表规则。

四表:

filter 负责过滤功能。对应 iptables_filter 模块
nat  网络地址转换。对应 iptable_nat 模块
mangle 对数据报文拆解、修改、重新封装的功能;对应 iptable_mangle 模块
raw 关闭nat表上启用的连接追踪机制;对应 iptable_raw 模块

五个链:

PREROUTING 是在包进入防火墙之后(入站)、路由决策之前做处理
POSTROUTING 是在路由决策之后(出站),做处理
INPUT  在包被路由到本地之后,但在出去用户控件之前做处理
OUTPUT在去顶包的目的之前做处理
FORWARD 在最初的路由决策之后,做转发处理

详细的请查看man iptables,下面我们只对常用的 filter 介绍一下其用法,这里省略了参数 -t filter

1、查看

iptables -nvL –line-number

-L 查看当前表的所有规则,默认查看的是 filter 表,如果要查看 NAT 表,可以加上 -t NAT 参数
-n 不对ip地址进行反查,加上这个参数显示速度会快很多
-v 输出详细信息,包含通过该规则的数据包数量,总字节数及相应的网络接口
–line-number 显示规则的序列号,这个参数在删除或修改规则时会用到

2、添加
添加规则有两个参数:-A-I 。其中 -A 是添加到规则的末尾;-I 可以插入到指定位置,没有指定位置的话默认插入到规则的首部。

Continue reading

iptables 开放80端口

iptables -F //清空规则

iptables -A INPUT -p tcp –dport 22 -j ACCEPT /*允许包从22端口进入*/
iptables -A OUTPUT -p tcp –sport 22 -m state –state ESTABLISHED -j ACCEPT /*允许从22端口进入的包返回*/
iptables -A OUTPUT -p udp –dport 53 -j ACCEPT
iptables -A INPUT -p udp –sport 53 -j ACCEPT
iptables -A INPUT -s 127.0.0.1 -d 127.0.0.1 -j ACCEPT /*允许本机访问本机*/
iptables -A OUTPUT -s 127.0.0.1 -d 127.0.0.1 -j ACCEPT
iptables -A INPUT -p tcp -s 0/0 –dport 80 -j ACCEPT /*允许所有IP访问80端口*/
iptables -A OUTPUT -p tcp –sport 80 -m state –state ESTABLISHED -j ACCEPT
iptables-save > /etc/sysconfig/iptables /*保存配置或者使用命令 service iptables save */
iptables -L

上面的80和22可以简写成:

[shell]/sbin/iptables -I INPUT -p tcp –dport 80 -j ACCEPT
/sbin/iptables -I INPUT -p tcp –dport 22 -j ACCEPT [/shell]

禁止192.168.0.7连接memcached

[shell]iptables -A INPUT -s 192.168.0.7 -p tcp –dport 11211 -j DROP[/shell]

只允许指定ip访问指定的端口

iptables -A INPUT -s 74.82.164.142 -p tcp –dport 9306 -j ACCEPT
iptables -A INPUT -p tcp –dport 9306 -j DROP # 如果在iptables表最下面有一条拒绝所有规则以外的规则的话,则这行不用写。

详细教程参考:http://blog.haohtml.com/archives/13649

iptables命令

语法
iptables [-t 要操作的表]
<操作命令>
[要操作的链]
[规则号码]
[匹配条件]
[-j 匹配到以后的动作]
 操作命令(-A、-I、-D、-R、-P、-F)
 查看命令(-[vnx]L)
如以下命令,其中-t filter为可选项,一般情况下省略不写:
iptables -t filter -A INPUT -j DROP
iptables命令参数
-A 添加规则到规则链表 iptables -A INPUT
-D 从规则链表中删除规则,可是完整规则,也可以是规则编号
-R 取代现行规则,不改变在链中的顺序如:iptables -R INPUT 1 -s 193.168.0.1 -j DROP
-I 插入一条规则 如:iptables -I INPUT 1 –dport 80 -j DROP
-L 列出某规则链中所有规则
-F 删除某规则链中所有规则
-Z 将封包计数器清零
-N 定义新的规则链
-X 删除某个规则链
-P 定义过滤政策
-E 修改自定义规则链名字
常用处理动作(用j参数指定):
ACCEPT:放行。直接跳往下一个规则链;
REJECT:阻拦。处理后不再对比其他规则,直接中断过滤程序并传送消息(ICMP port-unreachable、
ICMP echo-reply tcp-reset)给对方。如: -j REJECT –reject-with tcp-reset
DROP:丢弃包。直接中断过滤程序; Continue reading

LINUX下iptables的命令应用

手册:http://docs.haohtml.com/download/linux/2%20%d0%a1%ca%b1%cd%e6%d7%aa%20iptables%20%c6%f3%d2%b5%b0%e6%20v1.5.4.pdf

iptables命令
维护规则表的命令:

1. (-N)创建一个新规则表

2. (-X)删除一个空规则表

3. (-P)改变内建规则表的默认策略

4. (-L)列出规则表中的规则

5. (-F)清空规则表中的规则

6. (-Z)将规则表计数器清零

管理规则表中的规则:

1. (-A)添加新规则到规则表

2. (-I)插入新规则到规则表的某个位置 Continue reading

Linux中Iptables命令详解

手册:http://docs.haohtml.com/download/linux/2%20%d0%a1%ca%b1%cd%e6%d7%aa%20iptables%20%c6%f3%d2%b5%b0%e6%20v1.5.4.pdf

用iptables -ADC 来指定链的规则,-A添加 -D删除 -C 修改

iptables – [RI] chain rule num rule-specification[option]
用iptables – RI 通过规则的顺序指定

iptables -D chain rule num[option]
删除指定规则

iptables -[LFZ] [chain][option]
用iptables -LFZ 链名 [选项]

iptables -[NX] chain
用 -NX 指定链

iptables -P chain target[options]
指定链的默认目标

Continue reading