iptables 详解

netfilter/iptables包过滤系统被称为单个实体,但它实际上由netfilteriptables两个组件组成。netfilter也称为内核空间(KernelSpace),是内核的一部分,由包过滤表组成,这些表包含内核用来控制包过滤处理的规则集;iptables也称为用户空间(UserSpace),是主要配置工具,iptables 使插入、修改和删除信息包过滤表中的规则变得容易。

netfilter

netfilter 选取了 5 个位置进行数据包操作:

  • PREROUTING:数据包流入网卡后进行路由前;
  • INPUT:数据包流入用户空间前;
  • FORWARD:设在不同的网卡之间;
  • OUTPUT:数据包流出用户空间前;
  • POSTROUTING:数据包进行路由后流出网卡前。

数据包流向

iptables - 数据包流向

  • 本机发出的包:本机进程 -> OUTPUT链 -> 路由选择 -> POSTROUTING链 -> 出口网卡
  • 本机收到的包:入口网卡 -> PREROUTING链 -> 路由选择 -> 此时有两种可能的情况:
    • 目的地址为本机:INPUT链 -> 本机进程
    • 目的地址不为本机:FORWARD链 -> POSTROUTING链 -> 网卡出口(内核允许网卡转发的情况下)

规则链和表

链、表、规则
首先需要搞清楚的三个概念就是:链(chain)表(table)规则(rule)

  • :即我们前面说的五个卡口(物理概念),PREROUTINGINPUTFORWARDOUTPUTPOSTROUTING
  • :为了方便管理而提出的逻辑概念,每个表都有其特定的功能;有rawmanglenatfilter四张表;
  • 规则:很容易理解,规则就是用于描述如何处理一个数据包的;描述一条规则时都会说明它所在的链和所在的表。

注意,后面会提到自定义链,自定义链与预定义的 5 个链虽然都叫做“链”,但是它们有着本质的不同,自定义链不是“自定义卡口”,它们只是一系列规则/动作的集合,一般用在 -j-g 选项中,-j 其实是 --jump,而 -g 则是 --goto,-j 除了可以指定自定义链外,还可以指定其它的 target,但是 -g 只能指定自定义链。它们的区别:-j 指定的链在匹配完成后会返回当前链继续匹配下一条规则,而 -g 指定的链在匹配完成后不会返回到当前链,如果在自定义链中没有规则与之匹配,则按照当前链的 policy 处理(ACCEPT 或者 DROP)。

表的优先级
所谓优先级就是处理的顺序,从左到右优先级依次降低:raw -> mangle -> nat -> filter

  • raw,优先级最高,通常与NOTRACK一起使用,用于跳过连接跟踪(conntrack)和 nat 表的处理;
  • mangle,修改包头部的某些特殊条目,如 TOS、TTL、打上特殊标记 MARK 等,以影响后面的路由决策;
  • nat,用于进行网络地址转换,如 SNAT(修改源地址)、DNAT(修改目的地址)、REDIRECT 重定向等;
  • filter,用于过滤数据包,比如 ACCEPT(允许),DROP(丢弃)、REJECT(拒绝)、LOG(记录日志);

raw 表除了 -j NOTRACK 外,还有一个常用的动作,那就是 -j TRACE,用于跟踪数据包,进行规则的调试,使用 dmesg 查看。

默认策略
默认策略只有两个动作:ACCEPT、DROP;当一个数据包未与该链中的任何规则匹配时,它将被默认策略处理;
也就是所谓的黑白名单,如果默认策略为 ACCEPT,则为黑名单模式,如果默认策略为 DROP,则为白名单模式。

连接跟踪
连接跟踪,顾名思义,就是跟踪并记录网络连接的状态(你可能认为只有 TCP 才有“连接”这个概念,但是在 netfilter 中,TCP、UDP、ICMP 一视同仁)。netfilter 会为每个经过网络堆栈的连接生成一个连接记录项(Connection Entry);此后所有属于此连接的数据包都被唯一地分配给这个连接并标识连接的状态;由所有连接记录项组成的表其实就是所谓的连接跟踪表

为什么需要连接跟踪?因为它是状态防火墙和 NAT 的实现基础

  • 状态防火墙:iptables 的 conntrack/state 模块允许我们根据连接的状态进行规则配置,如果没有连接跟踪,那是做不到的。
  • NAT,NAT 其实就是修改数据包的源地址/端口、目的地址/端口,如果没有连接跟踪,那么也不可能再找回修改前的地址信息。

SNAT 可以说在每个家用路由器上都有,不过它可能不叫做 SNAT,而是 MASQUERADE,但是本质都是 SNAT,只不过 MASQUERADE 会自动获取设备的 IP 地址而已,一般用于 DHCP 环境。假设网关的公网 IP 为 1.1.1.1,内网网段为 192.168.1.0/24,某台内网主机的 IP 为 192.168.1.1,该内网主机的某个进程想与 2.2.2.2 这台服务器进行通信,则发出的包为 [src: 192.168.1.1, dst: 2.2.2.2],路由到网关后,因为在网关的路由表中也没有匹配的条目,所以会发给默认路由(从公网网卡出去),不过发出去之前要先经过 POSTROUTING 链的处理,在 nat 表中该数据包的源地址会被转换为公网网卡的 IP 地址,即 1.1.1.1,也就是进行 SNAT,转换后变为 [src: 1.1.1.1, dst: 2.2.2.2],然后才会从公网网卡出去(如果不进行 SNAT,那么数据包会被丢弃);经过各级路由后,此数据包会到达 2.2.2.2 服务器,流入网卡,依次经过 PREROUTING -> 路由选择[发往本机] -> INPUT -> 本机进程,本机进程处理完后,数据包 [src: 2.2.2.2, dst: 1.1.1.1] 经过 OUTPUT -> 路由选择[发往外网] -> POSTROUTING -> 公网网卡 -> 各级路由后,到达 1.1.1.1 网关主机,流入网卡,数据包首先会被 PREROUTING 链处理,它会被一个隐式的 DNAT 规则处理(在 nat 表),将包的目的地址改为 192.168.1.1(通过读取连接记录项来获知),修改后的包为 [src: 2.2.2.2, dst: 192.168.1.1],然后再进行路由选择,因为不是发往本机的,所以会到 FORWARD 链,然后再到 POSTROUTING 链,最后才被送到内网网卡,最终流入 192.168.1.1 内网主机,被进程接收并处理。试想一下,如果没有连接跟踪,那么回程的 DNAT 操作就瞎了,内网主机 192.168.1.1 也就永远无法收到这个数据包。

Linux 默认会为所有连接都创建连接记录项,而维护连接跟踪表是有开销的,要命的是这个表还有大小限制;
因此,如果你在一个大流量的 Web 服务器上启用 iptables,很容易因为连接记录项过多而导致服务器拒绝连接!
那么有什么解决的办法呢?利用 raw 表的 NOTRACK 功能,让所有发往 80 端口的数据包跳过连接跟踪和 nat 表。
iptables -t raw -I PREROUTING -p tcp --dport 80 -j NOTRACK将所有发往 80/tcp 的数据包跳过 conntrack。

查看 conntrack 的使用情况、相关参数:

  • cat /proc/sys/net/netfilter/nf_conntrack_max:允许的最大连接记录项的数目,超过此值后会直接拒绝新连接
  • cat /proc/sys/net/netfilter/nf_conntrack_count:查看当前已使用的连接记录项数目,如果居高不下则应考虑优化
  • cat /proc/sys/net/netfilter/nf_conntrack_buckets:查看存储记录项的哈希桶的数目,默认为 nf_conntrack_max / 4

规则总体顺序

  1. 当一个数据包进入某个链时,首先按照表的优先级依次处理;
  2. 每个表中的规则都有序号(从 1 开始),数据包会根据规则序号依次进行匹配;
  3. 如果命中一条规则,则执行相应的动作;如果所有表的规则都未命中,则执行默认策略。

命令详解

应用例子

ipset模块

recent模块

iptables-recent 模块可有效预防 DDOS、CC 攻击;还可以避免 ssh 暴力破解,具体请参考 - ssh 防暴力破解