欢迎光临
我们一直在努力

iptables 详解

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

netfilter

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

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

数据包流向

  • 本机发出的包:本机进程 -> 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. 如果命中一条规则,则执行相应的动作;如果所有表的规则都未命中,则执行默认策略。

命令详解

### 命令用法 (man 文档)
iptables [-t table] {-A|-C|-D} chain rule-specification         # (ipv4) 追加|检查|删除 规则
ip6tables [-t table] {-A|-C|-D} chain rule-specification        # (ipv6) 追加|检查|删除 规则
iptables [-t table] -I chain [rulenum] rule-specification       # 在 rulenum 处插入规则 (默认为 1)
iptables [-t table] -R chain rulenum rule-specification         # 替换第 rulenum 条规则
iptables [-t table] -D chain rulenum                            # 删除第 rulenum 条规则
iptables [-t table] -S [chain [rulenum]]                        # 打印指定 table 的规则
iptables [-t table] {-F|-L|-Z} [chain [rulenum]] [options...]   # 清空|列出|置零 链/规则
iptables [-t table] -P chain target                             # 设置默认策略
iptables [-t table] -N chain                                    # 新建自定义链
iptables [-t table] -X [chain]                                  # 删除自定义链
iptables [-t table] -E old-chain-name new-chain-name            # 重命名自定义链

### 一般形式
iptables [-t 表名] {-A|I|D|R} 链名 [规则号] 匹配规则 -j 动作

### 查看帮助
iptables --help             # 查看 iptables 的帮助
iptables -m 模块名 --help   # 查看指定模块的可用参数
iptables -j 动作名 --help   # 查看指定动作的可用参数

### 持久化规则
# 在 RHEL/CentOS 中,默认规则文件为 /etc/sysconfig/iptables
# 在 iptables 服务启动时,默认会加载该配置文件中定义的规则
# 如果想要设置的规则在重启后有效,就需要保存规则到这个文件
# 使用这两个工具:iptables-save、iptables-restore 重定向即可
## 保存当前规则
iptables-save >/etc/sysconfig/iptables
iptables-save >/etc/sysconfig/iptables.20171125
## 恢复当前规则
iptables-restore </etc/sysconfig/iptables
iptables-restore </etc/sysconfig/iptables.20171125

### 命令和选项
-t              # 指定要操作的表,如果省略则默认为 filter 表
-A              # "追加" 一条规则,只能追加到末尾
-I              # "插入" 一条规则,如果省略序号则默认为 1
-R              # "替换" 一条规则,必须指定序号
-D              # "删除" 一条规则,必须指定序号
-C              # "检查" 规则是否存在,如果存在则返回 0
-P              # 设置链的 "默认策略",nat 表不允许修改默认策略
-S              # "查看" 规则(原始格式)
-L              # "打印" 规则(友好格式)
-F              # 清空表中的规则并将包计数器、字节计数器置零
-Z              # 将某个链或某条规则的包计数器、字节计数器置零
-N              # 新建自定义链
-X              # 删除自定义链
-E              # 重命名自定义链
-n              # 以数字形式显示地址和端口
-v              # 在打印规则时显示详细信息
--line-numbers  # 在打印规则时显示规则序号

### 反向匹配
# 只需在选项前面使用 ! 即可,如: ! -s 192.168.0.0/16 表示除 192.168/16 外的源 IP

### 通用匹配
-s addr[/mask][...] # 源 IP,可以有多个,使用逗号隔开,有多少个地址就有多少条规则
-d addr[/mask][...] # 目的 IP,可以有多个,使用逗号隔开,有多少个地址就有多少条规则
-i input-nic[+]     # 数据包来自哪个网卡,+ 表示通配符
-o output-nic[+]    # 数据包送往哪个网卡,+ 表示通配符
-p {tcp|udp|udplite|icmp|icmpv6|esp|ah|sctp|mh|all}
                    # 指定匹配的协议,all 表示所有

### 隐式扩展匹配 (tcp/udp/icmp)
## tcp 扩展
--sport port[:port]     # 源端口号,100:200 表示端口范围
--dport port[:port]     # 目的端口号,100:200 表示端口范围
--tcp-flags mask comp   # TCP 标志位,flags={SYN|ACK|FIN|RST|URG|PSH|ALL|NONE}
--syn                   # SYN 标志位,等同于 --tcp-flags SYN,RST,ACK,FIN SYN
--tcp-option number     # TCP 选项

## udp 扩展
--sport port[:port]     # 源端口号
--dport port[:port]     # 目的端口号

## icmp 扩展
--icmp-type name[/code] # icmp 类型,常用的两个: ping、pong,请求和应答

### 显式扩展匹配 (-m module-name)
## multiport 多端口
--sports port[,port:port,port...]   # 匹配多个源端口[范围]
--dports port[,port:port,port...]   # 匹配多个目的端口[范围]
--ports port[,port:port,port...]    # 匹配多个源和目的端口[范围]

## owner 所属用户(组)
--uid-owner userid[-userid]     # 匹配 UID[范围]/username
--gid-owner groupid[-groupid]   # 匹配 GID[范围]/groupname
--socket-exists                 # 匹配与套接字相关联的数据包

## state 连接状态(基本)
--state [INVALID|ESTABLISHED|NEW|RELATED|UNTRACKED][,...]
# NEW           新连接
# ESTABLISHED   已建立的连接
# RELATED       关联的连接,如 ftp
# INVALID       无效/非法的连接
# UNTRACKED     未启用连接跟踪的连接

## conntrack 连接状态
--ctstate {INVALID|ESTABLISHED|NEW|RELATED|UNTRACKED|SNAT|DNAT}[,...]

## iprange IP范围
--src-range ip[-ip]     # 匹配指定的源 IP 范围
--dst-range ip[-ip]     # 匹配指定的目的 IP 范围

## mac 源MAC地址
--mac-source XX:XX:XX:XX:XX:XX  # 匹配源 MAC 地址

## addrtype 地址类型
--src-type type[,...]   # 匹配 source ip 地址的类型
--dst-type type[,...]   # 匹配 destination ip 地址的类型
# UNSPEC       未指定的地址,如 0.0.0.0
# UNICAST      单播地址
# LOCAL        本机地址
# BROADCAST    广播地址
# ANYCAST      任播地址
# MULTICAST    多播地址
# BLACKHOLE    黑洞地址
# UNREACHABLE  不可达地址
# PROHIBIT     禁止访问的地址

## time 时间
--datestart time     # 起始日期 YYYY[-MM[-DD[Thh[:mm[:ss]]]]]
--datestop time      # 结束日期 YYYY[-MM[-DD[Thh[:mm[:ss]]]]]
--timestart time     # 起始时间 hh:mm[:ss]
--timestop time      # 结束时间 hh:mm[:ss]
--monthdays value    # 几号,1 至 31,默认为 all
--weekdays value     # 星期几,1 至 7,默认为 all
--kerneltz           # 使用内核时区而非 UTC

## string 关键字匹配
--from offset       # 设置起始偏移量
--to offset         # 设置结束偏移量
--algo {bm|kmp}     # 指定使用的算法
--icase             # 忽略大小写匹配
--string string     # 要匹配的字符串
--hex-string string # 要匹配的字符串,十六进制

## length 数据包大小
--length length[:length]    # 匹配数据包大小[范围]

## mark 防火墙标记
--mark value[/mask]     # 匹配有指定防火墙标记的数据包

## connmark "连接"标记
--mark value[/mask]     # 如果当前数据包所属的"连接"打了给定的标记,则匹配成功

## limit 速率限制
--limit avg             # 平均速率限制,如 3/hour,单位: sec|minute|hour|day
--limit-burst number    # 封顶速率限制,默认为 5

## connlimit 并发连接数
--connlimit-upto n      # 0..n
--connlimit-above n     # > n

### -j target 动作
## 无参数系列
ACCEPT      # 接收数据包
DROP        # 丢弃数据包
CUST-CHAIN  # 进入自定义链
RETURN      # 退出当前链,分两种情况:
            # 如果当前链是自定义链,则返回调用链,继续匹配调用点的下一条规则
            # 如果当前链不是自定义链,则执行当前链的默认策略,如 ACCEPT、DROP

## 带参数系列
# REJECT 拒绝(带原因)
--reject-with type              # 丢弃数据包,并回复源主机,可用的 type 如下:
    icmp-net-unreachable        # ICMP 网络不可达
    net-unreach                 # 同上,别名
    icmp-host-unreachable       # ICMP 主机不可达
    host-unreach                # 同上,别名
    icmp-proto-unreachable      # ICMP 协议不可达
    proto-unreach               # 同上,别名
    icmp-port-unreachable       # ICMP 端口不可达(默认)
    port-unreach                # 同上,别名
    icmp-net-prohibited         # ICMP 网络限制
    net-prohib                  # 同上,别名
    icmp-host-prohibited        # ICMP 主机限制
    host-prohib                 # 同上,别名
    icmp-admin-prohibited       # ICMP 管理员限制
    admin-prohib                # 同上,别名
    tcp-reset                   # TCP RST 连接重置
    tcp-rst                     # 同上,别名

# MARK 打防火墙标记
--set-mark value[/mask]   # 设置标记,mask OR value(推荐)
--set-xmark value[/mask]  # 设置标记,mask XOR value
--and-mark bits           # 设置二进制标记,AND
--or-mark bits            # 设置二进制标记,OR
--xor-mask bits           # 设置二进制标记,XOR

# CONNMARK 打"连接"标记
--set-mark value[/mask]       # 设置当前数据包所属连接的连接标记
--save-mark [--mask mask]     # 将当前数据包的标记作为其所属连接的标记
--restore-mark [--mask mask]  # 将当前数据包所属连接的标记作为该数据包的标记

# LOG 防火墙日志,dmesg 可查看
--log-level level       # 日志级别(syslog.conf)
--log-prefix prefix     # 日志前缀字符串
--log-tcp-sequence      # 记录 TCP seq 序列号
--log-tcp-options       # 记录 TCP options 选项
--log-ip-options        # 记录 IP options 选项
--log-uid               # 记录套接字相关联的 UID
--log-macdecode         # 解析 MAC 地址和协议

# ULOG 防火墙日志,kernel 2.4+
--ulog-nlgroup nlgroup  # 记录的 NETLINK 组
--ulog-cprange size     # 要复制的字节数
--ulog-qthreshold       # 内核队列的消息阈值
--ulog-prefix prefix    # 日志前缀字符串

# NFLOG 防火墙日志,kernel 2.6+
--nflog-group NUM       # 记录的 NETLINK 组
--nflog-size NUM        # 要复制的字节数
--nflog-threshold NUM   # 内核队列的消息阈值
--nflog-prefix STRING   # 日志前缀字符串

# SNAT 源地址转换,用在 POSTROUTING、INPUT 链
--to-source [<ipaddr>[-<ipaddr>]][:port[-port]]
--random        # 映射到随机端口号
--random-fully  # 映射到随机端口号(PRNG 完全随机化)
--persistent    # 映射到固定地址

# DNAT 目的地址转换,用在 PREROUTING、OUTPUT 链
--to-destination [<ipaddr>[-<ipaddr>]][:port[-port]]
--random        # 映射到随机端口号
--persistent    # 映射到固定地址

# MASQUERADE 源地址转换(适用于 DHCP 动态 IP),用在 POSTROUTING 链
--to-ports <port>[-<port>]  # 映射到指定端口号[范围]
--random                    # 映射到随机端口号

# REDIRECT 目的地址转换(重定向至 localhost:端口号),用在 PREROUTING、OUTPUT 链
--to-ports <port>[-<port>]  # 重定向到指定端口号[范围]
--random                    # 重定向到随机端口号

应用例子

## 查看当前规则
iptables -t raw -nvL    # raw 表
iptables -t mangle -nvL # mangle 表
iptables -t nat -nvL    # nat 表
iptables -t filter -nvL # filter 表,默认

## 查看当前规则,并显示规则序号
iptables -t raw -nvL --line-numbers     # raw 表
iptables -t mangle -nvL --line-numbers  # mangle 表
iptables -t nat -nvL --line-numbers     # nat 表
iptables -t filter -nvL --line-numbers  # filter 表,默认

## 查看当前规则,简版
iptables -t raw -S
iptables -t mangle -S
iptables -t nat -S
iptables -t filter -S

## 允许来自其它主机的 ssh 连接,假设为默认端口号 22
iptables -t filter -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT

## 允许来自其它主机的 http 连接,假设为默认端口号 80
iptables -t filter -A INPUT -p tcp -m tcp --dport 80 -j ACCEPT

## 拒绝所有 icmp 数据包,即禁止其它主机 ping、traceroute 本机
iptables -t filter -A INPUT -p icmp -j REJECT --reject-with host-unreach    # 主机不可达
iptables -t filter -A INPUT -p icmp -j DROP                                 # 或直接丢弃

## 限制 icmp 速率,一分钟平均只能 ping 20 次,最多 30 次
iptables -t filter -A INPUT -p icmp -m limit --limit 20/min --limit-burst 30 -j ACCEPT
iptables -t filter -A INPUT -p icmp -j REJECT --reject-with host-unreach # 否则直接丢弃

## 将发往 80/tcp、443/tcp 的数据包跳过连接跟踪和 nat 表的处理
iptables -t raw -I PREROUTING -p tcp -m multiport --dports 80,443 -j NOTRACK

## 将当前主机作为内网网关,做源地址转换
iptables -t nat -A POSTROUTING -s 192.168/16 ! -d 192.168/16 -j SNAT --to-source 12.34.56.78 # 假定公网 IP 为 12.34.56.78
iptables -t nat -A POSTROUTING -s 192.168/16 ! -d 192.168/16 -j MASQUERADE                   # 假定公网 IP 为 DHCP 动态 IP

## 将所有 TCP 流量(包括内网的)重定向至本机代理软件,监听端口为 12345
iptables -t nat -A PREROUTING -p tcp -s 192.168/16 -j REDIRECT --to-ports 12345
iptables -t nat -A OUTPUT -p tcp -j REDIRECT --to-ports 12345

## 添加规则、检查规则、删除规则
iptables -t filter -A OUTPUT -d 1.2.3.4 -j ACCEPT # 追加到末尾
iptables -t filter -I OUTPUT -d 1.2.3.4 -j ACCEPT # 插入到开头
iptables -t filter -C OUTPUT -d 1.2.3.4 -j ACCEPT # 检查规则是否存在,存在返回 0,不存在返回 1
iptables -t filter -D OUTPUT -d 1.2.3.4 -j ACCEPT # 删除匹配的规则,如果有多条则只会删除其中一条

## 清空所有链的规则(不删除自定义链)
iptables -t raw -F
iptables -t mangle -F
iptables -t nat -F
iptables -t filter -F

## 清空所有链的规则(并删除自定义链)
iptables -t raw -F
iptables -t raw -X
iptables -t mangle -F
iptables -t mangle -X
iptables -t nat -F
iptables -t nat -X
iptables -t filter -F
iptables -t filter -X

ipset模块

## 为什么需要 ipset 模块?
# 假设这么种情况,我现在要让本机所有 TCP 流量走代理,但是大陆地址要走直连
# 这时候我们会想到使用 iptables 添加白名单,放行所有发往大陆地址的数据包,如:

# 新建自定义链 CHNIP,存放所有大陆地址段
iptables -t mangle -N CHNIP
iptables -t mangle -A CHAIN -d 地址段1 -j ACCEPT
iptables -t mangle -A CHNIP -d 地址段2 -j ACCEPT
...
iptables -t mangle -A CHAIN -d 地址段N  -j ACCEPT

# 放行所有目的地址为大陆地址的 TCP 数据包
iptables -t mangle -A OUTPUT -p tcp -j CHNIP

# 但是,大陆地址段是很多的,粗略统计也有 5000+ 条,这么多条 iptables 规则是会严重拉低性能的!
# 这时候就需要 ipset 出场了,利用 ipset 的 hash 表将 O(n) 的时间复杂度将为 O(1),具体用法如下:

# RHEL/CentOS 安装 ipset
yum -y install ipset
# ArchLinux 安装 ipset
pacman -S --needed ipset

# 创建 chnip 大陆地址段列表
ipset -N chnip hash:net

# 添加大陆地址 IP 段到列表中
ipset -A chnip 地址段1
ipset -A chnip 地址段2
...
ipset -A chnip 地址段N

# 添加一条 iptables 规则即可
iptables -t mangle -A OUTPUT -p tcp -m set --match-set chnip dst -j ACCEPT

## ipset 相关用法
ipset -L chnip                 # 列出大陆地址段列表
ipset -F chnip                 # 清空大陆地址段列表
ipset -X chnip                 # 删除大陆地址段列表
ipset -S chnip >/etc/ipset.set # 保存大陆地址段列表
ipset -R       </etc/ipset.set # 恢复大陆地址段列表

ipset -L                       # 查看所有 ipset 集合
ipset -F                       # 清空所有 ipset 集合
ipset -X                       # 删除所有 ipset 集合
ipset -S                       # 导出所有 ipset 集合
ipset -R                       # 导入所有 ipset 集合
ipset -E old_set new_set       # 对 ipset 进行重命名
赞(0) 打赏

评论 抢沙发

评论前必须登录!

 

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏