Linux iptables

2018-04-30|Categories: External cmd, Linux, Network|Tags: |

本文介绍iptables的基本概念、工作原理,以及iptables的一些简单应用场景。

Iptables简介

Iptables是Linux系统的一个命令行工具,运行在用户空间,功能强大,操作方便。CentOS 7之前的系统默认用它管理防火墙规则,以及配置NAT(Network Address Translation,网络地址转换)。

与CentOS 7默认启用的另一款防火墙管理工具firewalld相比,iptables让用户更接近防火墙的底层工作原理,而firewalld是基于iptables做了二次开发,把底层原理隐藏起来,提供更简单直观的用户界面,让初学者更容易上手,同样也可以提升熟练用户的管理效率。二者各有优缺点,但对于系统管理员而言,掌握底层工作原理是必须的,因此熟练使用iptables是必备技能。

Iptables工作原理

实际上,所有的包过滤、NAT工作都是由内核Netfilter模块执行的,iptables只是一个与Netfilter交互的命令而已。

Netfilter、iptables都是由The Netfilter.org Project开发。

Netfilter/iptables有以下几个重要的概念必须先弄清楚:

  • 表(table)
  • 链(chain)
  • 目标(target)

表 table

表是iptables的主要组成部分,iptables有四个表:

  • filter:过滤规则表(未使用-t选项时的默认表)
  • nat:地址转换规则表
  • mangle:修改数据标记位规则表
  • raw:关闭NAT表启用的连接跟踪机制,加快数据包穿越防火墙速度

当相同的规则出现在多个表时,优先级如下:

# 优先级从高到低
raw --> mangle --> nat --> filter

可以用如下命令查看表包含的链:

iptables -vnL -t nat

链 chain

用户通过iptables命令设置规则来定义Netfilter的工作方式,而规则必须关联到特定的链,每个链可以包含多条规则。当一个数据包到达本机,iptables会根据数据包所处的链,从上到下检查每一条规则,一旦发现数据包匹配某条规则,就跳转(jump)到规则定义的目标(target),同时停止检查后续规则。如果数据包没有匹配任何规则,则匹配默认策略(policy),iptables的默认策略是ACCEPT,如果对安全性有较高的要求,也可以修改为DROP

Iptables有五个链:

  • INPUT
  • OUTPUT
  • FORWARD
  • PREROUTING
  • POSTROUTING

分别与Netfilter提供的五个同名内核钩子函数一一对应。每个链表示数据包在内核中被处理的阶段:

用途
PREROUTING 数据包进入网卡,尚未查询路由表时处于PREROUTING链
INPUT 查询路由表之后,确定是发送给本机的数据包进入INPUT链,然后根据目标端口发送给相应的进程
FORWARD 查询路由表之后,确定不是发送给本机的数据包进入FORWARD链
OUTPUT 由本机发出的数据包进入OUTPUT链
POSTROUTING 所有转发的、本机发送的数据包在查询路由表之后、交给网卡处理之前进入POSTROUTING链

在五个链之间会有三种可能的数据流向:

  • 转发:PREROUTING –> FORWARD –> POSTROUTING
  • 流入:PREROUTING –> INPUT –> User-space process
  • 流出:User-space process –> OUTPUT –> POSTROUTING

目标 target

所谓「目标」其实就是对数据包执行的动作。iptables内建的目标有很多,例如:

  • ACCEPT
  • DROP(没有回应数据包)
  • REJECT(有回应数据包,告知请求被拒绝)
  • SNAT
  • DNAT
  • MASQUERADE
  • REDIRECT
  • MARK
  • LOG
  • RETURN
    • 返回到调用此条(自定义链)规则的那个(iptables内建)链
    • 类似Shell脚本的break关键字

用户自定义目标

用户也可以自定义目标,实质就是自定义链,可以把自定义链理解为iptables的「函数」。下面是一个自定义目标的示例:

# 自定义一个链,名为 invalid_packet
iptables -N invalid_packet

# 给自定义链改名
iptables -E invalid_packet INVALID_PKT

# 向自定义链添加规则
iptables -A INVALID_PKT -p tcp --tcp-flags ALL ALL -j REJECT
iptables -A INVALID_PKT -p tcp --tcp-flags ALL NONE -j REJECT

# 调用自定义链(当做target)
iptables -I INPUT -j INVALID_PKT

# 删除自定义链
iptables -X INVALID_PKT
# 必须首先删除调用,否则报错:iptables: Too many links.
# 然后清空规则,否则报错:iptables: Directory not empty.

Iptables配置前提:先为不可胜

孙子兵法说:昔之善战者,先为不可胜,以待敌之可胜。设置iptables规则也是一样,我们要阻止不需要的访问,但不能把我们自己的访问也阻止掉。为了避免发生这种不愉快的情况,我们需要先「保护」好自己。

危险规则

这里的「危险」的意思是,某些规则被添加之后,会导致设置规则的用户自身也被防火墙阻挡,无法远程访问主机。如果只是在本地的虚拟机上测试,登录虚拟机控制台(Console)删除规则就行;但如果是远程主机,可能会非常麻烦。例如:

# 禁止所有IP访问,包括设置规则的用户自身
iptables -A INPUT -j REJECT

# 和前一条规则完全等效
iptables -A INPUT -s 0.0.0.0/0 -j REJECT

因此,类似这种规则添加前必须做好保护措施

# 假设我从 172.16.125.71 访问远程主机
iptables -I INPUT -s 172.16.125.71 -j ACCEPT

这条规则被添加为INPUT链的第一条规则,意为始终允许来自指定IP地址的访问。

是否需要修改默认策略为DROP

从提高安全性的角度来说,修改默认策略为DROP更加安全:

# 把INPUT链的默认策略改为DROP
iptables -P INPUT DROP

但是,安全和便利永远是一对矛盾,假设默认策略为DROP的情况下不留神执行了下面这条命令,清空了所有规则:

iptables -F

此时就会发生和前一小节相同的情况:设置规则的用户自身也被防火墙禁止远程访问!因此,如果要使用更严格的规则,用户必须对可能发生的情况有充分预期,并准备好相应的措施,避免「杀敌三千,自损一万」。

清理内建规则

如果不需要系统内建的规则,可以用iptables -F全部清除。但某些服务启动时会自动添加规则,可以考虑禁用这些服务。

禁用iptables服务

service iptables stop
chkconfig iptalbes off

注意,上面的命令是禁止开机执行/etc/rc.d/init.d/iptables脚本,不是禁用iptables这个命令。

禁用firewalld服务

CentOS 7虽然推荐使用firewalld,但iptables仍然可以正常运行,但二者只能选一,我选择禁用firewalld:

systemctl stop firewalld
systemctl disable firewalld

清理virbr0虚拟网卡生成的规则

CentOS 7默认启用的虚拟网卡virbr0自动创建了许多iptables规则:

virbr0虚拟网卡是由libvirtd服务创建的默认网络配置(default network configuration),libvirtd服务为Linux提供基础的虚拟化组件,这些组件可用于KVM、Xen、VMware ESX、QEMU等虚拟化技术。如果不需要用到这些技术,可以将virbr0虚拟网卡临时禁用:

virsh net-destroy default

# virsh 可用以下命令安装
yum install libvirt-client

但系统重新启动之后,这个虚拟网卡又会出现,此时可以禁止libvirtd服务开机启动:

systemctl disable libvirtd

Iptables规则示例

以下所有示例基于一个前提:所有链的默认策略为ACCEPT

仅允许SSH访问

iptables -I INPUT -p tcp --dport 22 -j ACCEPT
iptables -A INPUT -j REJECT

允许SSH、HTTP、HTTPS访问

在上一小节的基础上修改规则:

iptables -R INPUT 1 -m multiport -p tcp --dports 22,80,443 -j ACCEPT

这里的-m multiport表示显式启用multiport扩展模块,然后可以通过--dports选项同时指定多个端口。注意,上一小节指定单个端口用的选项是--dport,末尾没有s。

允许从本机ping其它主机,不允许被ping

基于以上规则,添加规则:

iptables -I INPUT 2 -p icmp --icmp-type echo-reply -j ACCEPT

意思是,接受ICMP的echo-reply类型的数据包,也就是ping命令的回应包,回应包也可以用数字0表示,更简洁。请求包的ICMP类型是echo-request或者数字8

我尝试过用iptables -A INPUT -m limit --limit 10/minute -p icmp -j ACCEPT应对ping -f(Flood ping)泛洪攻击,但效果不佳,CPU占用只是比未设置规则时要低,但比正常使用时明显要高。

允许FTP被动模式访问

对于FTP服务器而言,FTP被动模式的特点是:

  • 控制命令通道使用TCP/21端口
  • 数据传输通道使用随机端口

由于每次数据传输都会使用不同的端口,所以无法基于端口来制定规则,而必须基于连接状态制定规则。iptables的连接状态和TCP协议的有限状态机(Finite State Machine)无关,实际上是指数据包与连接之间的关系,由内核模块nf_conntrack来追踪,iptables通过state扩展指定追踪状态,具体有以下几种:

  • INVALID
    • 数据包没有关联到已知的连接
  • NEW
    • 数据包来自新的连接,或是关联到的连接在来回两个方向上都没有数据包
  • ESTABLISHED
    • 数据包关联到一个来回两个方向都有数据包的连接
  • RELATED
    • 数据包来自新的连接,但与现有的连接有关联,例如FTP数据传输或ICMP错误
  • UNTRACKED
    • 数据包完全未被追踪,在raw表通过-j CT --notrack显式指定不追踪数据包时会发生这种情况

使用state扩展会自动加载nf_conntrack模块,可以通过以下命令查看模块加载状态:

lsmod | grep nf_conntrack

# 万一没有自动加载,可以手动加载
modprobe nf_conntrack

还需要加载专门追踪FTP流量的模块:

modprobe nf_conntrack_ftp

此时就可以添加iptables规则了:

iptables -I INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -I INPUT 3 -m state --state NEW -p tcp --dport 21 -j ACCEPT

需要注意的是:nf_conntrack模块需要消耗较多资源,如果主机的连接数较多,需要多关注CPU和内存的状况。

以下是nf_conntrack模块的相关参数:

  • /proc/net/nf_conntrack:已经追踪到的并记录下来的连接信息库
  • /proc/sys/net/nf_conntrack_max:连接追踪功能所能够容纳的最大连接数量,建议根据系统实际情况把这个值调大
    echo "net.nf_conntrack_max = 655360" >> /etc/sysctl.conf
    # 生效后 net.netfilter.nf_conntrack_max 会被自动改为相同的值
    
    # 让新的内核参数生效
    sysctl -p
    
  • /proc/sys/net/netfilter/:不同的协议的连接追踪时长

工作时间内禁止访问视频网站

首先自定义一个链,包含所有禁止访问的视频网站:

# 自定义链,名为 VideoSites
iptables -N VideoSites

# v.qq.com
iptables -A VideoSites -p tcp -m multiport --dports 80,443 \
            -m string --algo bm --string "v.qq.com" -j REJECT

# youku.com
iptables -A VideoSites -p tcp -m multiport --dports 80,443 \
            -m string --algo bm --string "youku.com" -j REJECT

# iqiyi.com
iptables -A VideoSites -p tcp -m multiport --dports 80,443 \
            -m string --algo bm --string "iqiyi.com" -j REJECT

以上的规则就是通过string扩展检查HTTP和HTTPS报文是否包含视频网站的域名,检测到了就拒绝访问。

然后调用自定义链:

iptables -A OUTPUT -m time --weekdays 1,2,3,4,5 \
            --timestart 1:00 --timestop 10:00 -j VideoSites

time扩展指定规则生效的时间是每周一到周五,每天的1:0010:00(UTC时间)。需要注意的是,时间必须是UTC格式,所以这里的1:0010:00换算成北京时间就是9:0018:00

string扩展应该选用哪种字符串搜索算法

string扩展支持两种字符串搜索算法:

--algo {bm|kmp}
  • bm = Boyer-Moore
  • kmp = Knuth-Pratt-Morris

那么二者究竟孰优孰劣?我不懂算法,但网络搜索的结果都说bm算法比kmp算法更快。例如:

《字符串匹配的Boyer-Moore算法》说:

KMP算法并不是效率最高的算法,实际采用并不多。各种文本编辑器的「查找」功能(Ctrl+F),大多采用Boyer-Moore算法

《grep之字符串搜索算法Boyer-Moore由浅入深(比KMP快3-5倍)》说:

在用于查找子字符串的算法当中,BM(Boyer-Moore)算法是目前被认为最高效的字符串搜索算法,它由Bob Boyer和J Strother Moore设计于1977年。一般情况下,比KMP算法快3-5倍。该算法常用于文本编辑器中的搜索匹配功能,比如大家所熟知的GNU grep命令使用的就是该算法,这也是GNU grep比BSD grep快的一个重要原因,具体推荐看下我最近的一篇译文《为什么GNU grep如此之快?》,作者是GNU grep的编写者Mike Haertel。

string扩展为何能够检查HTTPS数据包

先看tcpdump抓包结果:

抓包命令是:

tcpdump -nnvvvttttSXs 0 -i ens37 dst port 443

访问网页的命令是:

curl https://v.qq.com/

从上图红色方框处可以看到,两个数据包都包含了v.qq.com关键字,应该是HTTP报文Host首部的值。不是说HTTPS会把所有HTTP报文加密吗,为什么抓包还是可以看到?原因如下:

The whole lot is encrypted – all the headers. That's why SSL on vhosts doesn't work too well – you need a dedicated IP address because the Host header is encrypted.

The Server Name Identification (SNI) standard means that the hostname may not be encrypted if you're using TLS. Also, whether you're using SNI or not, the TCP and IP headers are never encrypted. (If they were, your packets would not be routable.)

https://stackoverflow.com/questions/187655/are-https-headers-encrypted

简而言之,如果服务器启用了SNI功能,那么HTTPS不会加密Host首部。

SNI就是在相同IP地址上提供多个HTTPS网站,并使用不同证书。新版的Nginx默认启用了SNI功能。

string扩展无法直接匹配DNS查询中的域名

本小节内容来自《Iptables drop domain dns request packet》,有删改,感谢原作者。放到这里是防止链接失效。

DNS查询语句如下:

dig A www.abc.com

抓包语句和结果如下:

$ tcpdump -nnvvvttttX -i ens37 -nn src port 53
tcpdump: listening on ens37, link-type EN10MB (Ethernet), capture size 262144 bytes
2018-04-30 11:57:17.481470 IP (tos 0x0, ttl 38, id 57729, offset 0, flags [none], proto UDP (17), length 87)
    223.5.5.5.53 > 172.16.125.72.56033: [no cksum] 62558 q: A? www.abc.com. 2/0/0 www.abc.com. [15s] CNAME abc.com., abc.com. [15s] A 199.181.132.250 (59)
    0x0000:  4500 0057 e181 0000 2611 a5b1 df05 0505  E..W....&.......
    0x0010:  ac10 7d48 0035 dae1 0043 0000 f45e 8180  ..}H.5...C...^..
    0x0020:  0001 0002 0000 0000 0377 7777 0361 6263  .........www.abc
    0x0030:  0363 6f6d 0000 0100 01c0 0c00 0500 0100  .com............
    0x0040:  0000 0f00 02c0 10c0 1000 0100 0100 0000  ................
    0x0050:  0f00 04c7 b584 fa                        .......

可以看到,域名www.abc.com的DNS查询包并不是常规的对这个域名做HEX处理,其中并不包含圆点.字符(dot character),而是以圆点字符分隔域名,域名每部分的开始处加入这一部分的字符长度

 3 w  w w  3 a  b c  3 c  o m
0377 7777 0361 6263 0363 6f6d

所以wwwabccom前面分别是数字3。

把这个域名的DNS包DROP掉:

iptables -t raw -I dns_filter -m string --icase \
            --hex-string "|03|www|03|abc|03|com" --algo bm -j DROP

或者:

iptables -t raw -I dns_filter -m string --icase \
            --hex-string "|037777770361626303636f6d|" --algo bm -j DROP

最终生效的规则如下:

Chain dns_filter (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0           STRING match "|037777770361626303636f6d|" ALGO name bm TO 65535 ICASE

这里用成对的竖线|来标记不可打印的字符,之外的都被认为是普通ASCII字符。

另外,生效规则的末尾有TO 65535字符串,表示字符串搜索的范围是整个包。如果了解数据包的结构,也可以通过以下参数设置搜索范围,提升性能:

--from offset
              Set  the  offset  from  which  it starts looking for any matching. If not
              passed, default is 0.

--to offset
              Set the offset up to which should be  scanned.  That  is,  byte  offset-1
              (counting  from  0)  is  the  last  one  that is scanned.  If not passed,
              default is the packet size.

Iptables规则的保存与恢复

使用iptables命令定义的规则,手动删除之前,其生效期限为Kernel存活期限。换句话说,系统重启之后,没有保存的规则都会失效。所以,对于那些精心设置的规则,必须要将其保存下来,然后在系统启动时自动读取,方法如下:

# CentOS 6

# 备份规则
service iptables save
# 规则自动覆盖保存到 /etc/sysconfig/iptables 文件,
# iptables服务每次启动时读取这个文件
# CentOS 7

# 备份规则
iptables-save > ipt_rules.bak

# 恢复规则
iptables-restore < ipt_rules.bak
# -n, --noflush:不清除原有规则
# -t, --test:仅分析规则文件有无语法错误,但不提交

# 开机自动恢复规则
# 规则备份文件 ipt_rules.bak 必须存在
echo 'iptables-restore < ipt_rules.bak' >> /etc/rc.d/rc.local
chmod +x /etc/rc.d/rc.local

我们之前做的几个示例最终生成的规则如下:

$ iptables-save | tee ipt_rules.bak
# Generated by iptables-save v1.4.21 on Mon Apr 30 00:45:15 2018
*nat
:PREROUTING ACCEPT [47:3856]
:INPUT ACCEPT [15:1052]
:OUTPUT ACCEPT [898:66862]
:POSTROUTING ACCEPT [898:66862]
COMMIT
# Completed on Mon Apr 30 00:45:15 2018
# Generated by iptables-save v1.4.21 on Mon Apr 30 00:45:15 2018
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [78:9472]
:VideoSites - [0:0]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p tcp -m multiport --dports 22,80,443 -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 21 -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 0 -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-port-unreachable
-A OUTPUT -m time --timestart 01:00:00 --timestop 10:00:00 --weekdays Mon,Tue,Wed,Thu,Fri --datestop 2038-01-19T03:14:07 -j VideoSites
-A VideoSites -p tcp -m multiport --dports 80,443 -m string --string "v.qq.com" --algo bm --to 65535 -j REJECT --reject-with icmp-port-unreachable
-A VideoSites -p tcp -m multiport --dports 80,443 -m string --string "youku.com" --algo bm --to 65535 -j REJECT --reject-with icmp-port-unreachable
-A VideoSites -p tcp -m multiport --dports 80,443 -m string --string "iqiyi.com" --algo bm --to 65535 -j REJECT --reject-with icmp-port-unreachable
COMMIT
# Completed on Mon Apr 30 00:45:15 2018

更多资料

总结

管理网络流量是内核的主要工作之一,服务器上安装的应用程序大部分都需要处理网络流量,因此iptables的应用场景非常广泛,也是系统管理员必须掌握的技能之一。

限于篇幅,这里只介绍了iptables的基本用法,其它常用功能例如NAT详见《Iptables实现NAT》

Leave A Comment