使用 iptables 模拟网络故障

1. 前言

前一阵子线上程序出了一个 Downtime,原来同事写的一段代码中间在读写 Redis 的时候没有做好异常处理。在关键流程上面因为读写 Redis 异常直接退出了,导致后续的流程没有处理完。

解决方法也很简单,从业务逻辑入手在读写失败的时候进行重试和降级即可。可是怎么模拟网络故障进行测试呢?

下面就要轮到 iptables 上场了。

2. 使用 iptables 模拟故障

使用 iptables 来模拟网络故障的时候,我们针对 Redis 写入进行处理。简单来说就是在 Redis Server 端口 OUTPUT 的网络包分别进行 REJECT 和 DROP 操作。

sudo iptables -D OUTPUT -p tcp --destination-port 22368 -j REJECT
sudo iptables -D OUTPUT -p tcp --destination-port 22368 -j DROP

异常信息:

Caused by: java.net.SocketTimeoutException: connect timed out
        at java.net.PlainSocketImpl.socketConnect(Native Method)
        at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
        at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
        at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
        at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
        at java.net.Socket.connect(Socket.java:589)
        at redis.clients.jedis.Connection.connect(Connection.java:184)

测试过后虽然 Redis 写入数据有问题,但是主流程能够正常跑完,这就 OK 了。

iptables 使用简介

1. 常用语法

 iptables [-t <table>] [-I|-D|-A|-C|-R|-L] <chain> -p <protocol> --dport <port> -j DROP
 
 # 追加、检查、删除规则
 iptables [-t table] {-A|-C|-D} <chain> <rule-specification>
 # 插入规则
 iptables [-t table] -I <chain> [<rulenum>] <rule-specification>
 # 替换规则
 iptables [-t table] -R <chain> <rulenum> <rule-specification>
 # 删除规则
 iptables [-t table] -D <chain> <rulenum>
 # 持久化规则
 iptables [-t table] -S [<chain> [<rulenum>]]
 # 列举规则
 iptables [-t table] -L [<chain> [<rulenum>]] [options...]
 
 rule-specification = [matches...] [target]
 match = -m <matchname> [per-match-options]
 target = -j <targetname> [per-target-options]

其中:

  • -t <table>table 默认值为 filter,包含三种内建的 chainINPUTFORWARDOUTPUT

  • -A|-C|-D|-I|-R|-S|-L-A 追加规则,-C 检查规则,-D 表示删除规则,-I 表示插入规则,-R 替换规则,-S 持久化规则, -L 列举规则。

关于 rule-specification 的部分:

  • -p <protocol>:表示协议,比如 tcpudp

  • -s <address>[/<mask>][,...]:指定来源。

  • --dport <port>:目标端口。

  • -j <targetname>:包含ACCEPTDROPRETURN

2. 使用的简单示例

通常在设计分布式的模块时,都要考虑网络或者服务器故障时的降级与异常处理。因此使用 iptables 模拟网络故障就非常必要,下面给出一个简单的例子。

 # 首先启动一个 HTTP Server
 $ python -m SimpleHTTPServer
 Serving HTTP on 0.0.0.0 port 8000 ...
 
 # 列举所有 chain 
 $ sudo iptables -L
 Chain INPUT (policy ACCEPT)
 target     prot opt source               destination
 
 Chain FORWARD (policy ACCEPT)
 target     prot opt source               destination
 
 Chain OUTPUT (policy ACCEPT)
 target     prot opt source               destination
 
 # curl 一下
 $ curl localhost:8000
 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><html>
 <title>Directory listing for /</title>
 <body>
 <h2>Directory listing for /</h2>
 <hr>
 <ul>
 <li><a href=".bash_logout">.bash_logout</a>
 <li><a href=".bashrc">.bashrc</a>
 <li><a href=".lesshst">.lesshst</a>
 <li><a href=".profile">.profile</a>
 <li><a href=".ssh">.ssh</a>
 </ul>
 <hr>
 </body>
 </html>
 
 # 禁止 8000 端口入流量
 $ sudo iptables -I INPUT -p tcp --dport 8000 -j DROP
 
 # 再列举一下所有 chain
 $ sudo iptables -L
 Chain INPUT (policy ACCEPT)
 target     prot opt source               destination
 DROP       tcp  --  anywhere             anywhere             tcp dpt:8000
 
 Chain FORWARD (policy ACCEPT)
 target     prot opt source               destination
 
 Chain OUTPUT (policy ACCEPT)
 target     prot opt source               destination
 
 # 再次 curl 一下
 $ curl localhost:8000
 curl: (7) Failed to connect to localhost port 8000: Connection timed out
 
 # 删除禁用规则
 $ sudo iptables -D INPUT -p tcp --dport 2554 -j DROP

3. 参考资料