# 防火墙
# netfilter
相关历史:
- 1998 年,澳大利亚程序员 Rusty Russell 创建了 netfilter 项目,旨在改进 Linux 防火墙软件。
- 2000 年,Linux 2.4 内核发布,添加了 netfilter 框架,包括内核模块 ip_tables、ip6_tables、arp_tables、eb_tables 等。
netfilter 是 Linux 内核中的一个网络框架,提供了数据包过滤(包括无状态包、有状态包)、NAT 等功能。
- 目前大部分 Linux 发行版的防火墙软件,是基于 netfilter 实现的。
- 可执行
sysctl -a | grep net.netfilter
查看相关的内核参数。
netfilter 的工作原理:
- 用户使用 iptables 等命令配置防火墙规则,传给 netfilter 。
- netfilter 收到防火墙规则,转换成钩子(hook)函数,注册在 Linux 网络协议栈中。
- 当 Linux 内核每次接收、发送网络数据包时,就会触发 hook ,根据 hook 的逻辑来处理网络包,比如丢弃包、修改包。
netfilter 工作在内核态,并且提供了 xtables 工具集(它工作在用户态)。
- xtables 担任防火墙前端,供用户配置防火墙规则。包含以下命令行工具:还有以下命令,用于导入、导出防火墙规则:
iptables # 用于过滤 IPv4 协议的数据包 ip6tables # 用于过滤 IPv6 协议的数据包 arptables # 用于过滤 ARP 协议的数据包 ebtables # 用于过滤以太网帧
iptables-save iptables-restore ip6tables-save ip6tables-restore arptables-save arptables-restore ebtables-save ebtables-restore
- 不同网络协议的架构不同,因此它们的防火墙规则的语法不同,需要用不同的命令来配置。
- 不管用户配置了 iptables 规则,还是配置了 arptables 等规则,都会被 netfilter 转换成 hook 函数。
- xtables 担任防火墙前端,供用户配置防火墙规则。包含以下命令行工具:
# nf_conntrack
netfilter 属于有状态防火墙,默认会记录所有数据包的上下文信息,据此过滤数据包。
- 该功能由 netfilter 中的 conntrack 子模块实现,简称为 ct 。原理如下:
- hook 得到数据包。
- 识别数据包的状态。比如该数据包属于某个网络连接(包括 TCP、UDP、ICMP 等协议)。
- 根据数据包的信息,更新 nf_conntrack 中记录的网络连接。
- 该功能由 netfilter 中的 conntrack 子模块实现,简称为 ct 。原理如下:
nf_conntrack 采用哈希表结构,每行记录一个网络连接的信息(并不是每行记录一个数据包)。
- nf_conntrack 存储在 Linux 内核的内存中。
- 当 nf_conntrack 容量写满时,防火墙会拒绝新的 TCP、UDP 等网络连接。可执行
grep 'nf_conntrack: table full, dropping packet' /var/log/messages
查看已拒绝的日志。 - 如果一个网络连接一直没有传输数据,则超过 nf_conntrack_xx_timeout_xx 时长之后,会从 nf_conntrack 中删除其记录。
- 即使网络连接已关闭,也要等超时之后才能删除记录,从而跟踪网络连接的上下文信息。
- 减少 nf_conntrack_xx_timeout_xx 超时时间,能更快地删除无用的记录,节约 nf_conntrack 的容量。
例:
- 如果 client 与 server 建立 TCP 连接,则双方都绑定一个 ESTABLISHED 状态的 Socket ,在 nf_conntrack 中记录一些 ESTABLISHED 状态的数据包。
- 如果 client 主动关闭 TCP 连接,则:
- client 作为主动关闭方,Socket 会变为 TIME_WAIT 状态,等 2*MSL 时长之后才关闭。而 nf_conntrack 中依然记录该 TCP 连接,停留在 TIME_WAIT 状态,等 nf_conntrack_tcp_timeout_close_wait 超时之后才删除该记录。
- server 作为被动关闭方,会立即关闭 Socket 。而 nf_conntrack 中依然记录该 TCP 连接,停留在 TIME_WAIT 状态,等超时之后才删除该记录。
- 如果执行 ping 命令,向目标 IP 发送多个 ICMP 包,则 nf_conntrack 只会记录一条网络连接。
- 每发送一个新包,就会更新一次 nf_conntrack 中记录的网络连接,重新计算还剩多少秒可删除。
- 如果执行多次 ping 命令,即使目标 IP 相同, nf_conntrack 也会分别记录一条网络连接。
- 如果未成功建立网络连接,比如被防火墙拒绝了,则不会被 nf_conntrack 记录。
相关命令:
cat /proc/net/nf_conntrack # 查看 nf_conntrack 的当前内容 cat /proc/sys/net/netfilter/nf_conntrack_count # 查看 nf_conntrack 当前记录的连接数
例:
[CentOS ~]# cat /proc/net/nf_conntrack ipv4 2 tcp 6 75 TIME_WAIT src=10.0.0.1 dst=169.254.0.4 sport=56586 dport=80 src=169.254.0.4 dst=10.0.0.1 sport=80 dport=56586 [ASSURED] mark=0 zone=0 use=2 ipv4 2 tcp 6 85 TIME_WAIT src=10.0.0.1 dst=169.254.0.4 sport=56612 dport=80 src=169.254.0.4 dst=10.0.0.1 sport=80 dport=56612 [ASSURED] mark=0 zone=0 use=2 ipv4 2 udp 17 87 src=10.0.0.1 dst=183.60.83.19 sport=37967 dport=53 src=183.60.83.19 dst=10.0.0.1 sport=53 dport=37967 [ASSURED] mark=0 zone=0 use=2 ipv4 2 icmp 1 24 src=169.254.128.23 dst=10.0.0.1 type=8 code=0 id=13609 src=10.0.0.1 dst=169.254.128.23 type=0 code=0 id=13609 mark=0 zone=0 use=2
- 第一、二列表示网络层的协议名、协议号,比如 ipv4 的协议号是 2 。
- 第三、四列表示传输层的协议名、协议号。
- 第五列表示这行记录还剩多少秒可删除。
- 上例的第一行记录了一个 TCP 连接。
- 状态为 TIME_WAIT 。
- 第一组 src、dst 表示网络请求包的源地址、目标地址。
- 第二组 src、dst 表示网络回复包的源地址、目标地址。
- ASSURED 表示已记录请求包、回复包。如果只记录了请求包,还未出现回复包,则记作 UNREPLIED 。
nf_conntrack 中记录的数据包,可能添加多种状态(conntrack state ,简称为 ctstate)标记:
NEW # 该数据包属于一个新的网络连接,不属于已有的网络连接。例如 TCP、UDP、ICMP 通信时的第一个数据包 ESTABLISHED # 该数据包属于一个已有的网络连接。例如 TCP、UDP、ICMP 通信时的回复包,特点是数据包中的 src_address 与 dst_address 互换了 RELATED # 该数据包属于一个新的网络连接,并且关联已有的网络连接。这是因为某些网络协议,需要创建多个连接来通信 INVALID # 不能识别数据包的状态 UNTRACKED # 不跟踪该数据包的状态 SNAT # 该数据包的 src_address 被改了,因此收到回复包时,需要将其中的 dst_address 改回原值 DNAT
- 这些标记是数据包外部附加的信息,不会修改数据包的原本内容,不会传递到其它主机。
# iptables
- iptables 配置的防火墙规则,数据结构像表格。
- 总共内置了 5 个规则表(rule table)。
- 每个规则表包含多个规则链(rule chain)。
- 每个规则链包含多行规则。
# rule table
iptables 内置了 5 个规则表,用于让防火墙对数据包执行不同操作:
- raw
- :用于给数据包添加 UNTRACKED 标记,从而不被 netfilter 跟踪。
- 内置规则链:PREROUTING、OUTPUT
- mangle
- :用于修改数据包中的元数据,比如 TTL、MTU 。
- 内置规则链:全部 5 种
- nat
- :用于修改数据包中的 src_ip、src_port、dst_ip、dst_port 等元数据,从而实现端口转发、NAT 地址转换。
- DNAT 时的内置规则链:PREROUTING、OUTPUT
- SNAT 时的内置规则链:INPUT、POSTROUTING
- filter
- :用于过滤数据包,比如丢弃哪些数据包、放行哪些数据包。
- 内置规则链:INPUT、FORWARD、OUTPUT
- security
- :用于执行 SELinux 等强制访问控制(Mandatory Access Control,MAC)规则。
- 内置规则链:INPUT、FORWARD、OUTPUT
- raw
5 个规则表,会按照
raw > mangle > nat > filter > security
的优先级顺序,依次执行。- 大部分用户只会用到 nat、filter 两个表。
# rule chain
iptables 内置了 5 种规则链,用于在不同时间点处理数据包:
INPUT # 当网口接收数据包时 OUTPUT # 当网口发送数据包时 FORWARD # 数据包的源地址、目标地址都不是当前网口,说明需要路由转发到其它网口、其它主机 PREROUTING # 接收数据包时,并且尚未执行 IP 路由、INPUT 链 POSTROUTING # 发送数据包时,并且已执行 IP 路由、OUTPUT 链、FORWARD 链
- 数据包到达 INPUT、OUTPUT 或 FORWARD 链之前,都需要执行 IP 路由,决定将数据包发到何处。而 IP 路由之前,都需要执行 PREROUTING 链。
- 网口发送数据包时,可能发送到当前网口、本机的其它网口、其它主机。
- 如果数据包是本机生成的,则源地址是当前网口。
- 如果数据包是其它主机发送、由本机转发的,则源地址、目标地址都不是当前网口,因此不会执行 OUTPUT 链,而是执行 FORWARD 链。
- 即使网口发送数据包给自己,没有经过网络传输,也需要走一遍发送数据包、接收数据包的完整流程。
例:
- 本机生成并发送数据包时,流程如下:
- 本机进程将数据包写入 Socket 文件。
- 网口从 Socket 读取到数据包,依次执行 IP 路由、OUTPUT 链、POSTROUTING 链。
- 网口将数据包发出去。
- 本机接收数据包时,先执行 PREROUTING 链。然后执行 IP 路由:
- 如果数据包发向当前网口,则:
- 执行 INPUT 链。
- 正式接收数据包。例如将数据包写入 Socket 文件,供本机进程读取。
- 如果数据包发向其它网口、其它主机,则:
- 依次执行 FORWARD 链、POSTROUTING 链。
- 网口将数据包发出去。
- 如果数据包发向当前网口,则:
- 执行命令
ping localhost
时,流程如下:- 首先网口发送 ICMP 请求包:依次执行 OUTPUT 链、IP 路由、POSTROUTING 链。
- 然后网口接收 ICMP 请求包:依次执行 PREROUTING 链、IP 路由、INPUT 链。
- 本机生成并发送数据包时,流程如下:
netfilter 只支持在上述 5 个时间点创建 hook 函数,因此只内置了 5 种规则链。
- 用户不能创建新的规则表,但允许在内置规则表中创建新的规则链。
- 用户创建的 chain 没有绑定时间点,需要被内置 chain 引用,作为子链,才会生效。
- 例如在 INPUT 链中添加一行 rule ,其 target 为用户创建的 chain1 链。还可在 chain1 链中添加一行 rule ,其 target 为用户创建的 chain2 链。
# rule
每个 chain 默认包含 0 行规则(rule)。
- 用户可以在每个 chain 中添加任意行 rule 。
- 用户可以在一个 chain 中添加相同几行相同的 rule ,iptbales 不会自动去重。
- 每行 rule 的语法为
<match> <target>
,表示如果一个数据包匹配某条件,则执行某动作,又称为规则目标(rule target)。
iptables 内置了几种动作:
- ACCEPT
- :允许数据包通过。
- 不过数据包会经过多个规则表、多个 chain 处理,即使前面的环节用 ACCEPT 放行了数据包,后面的环节也可能拒绝数据包。
- REJECT
- :丢弃数据包,并回复一个拒绝访问的数据包给发送方。
- 不能用作 chain 的默认动作。
- 只允许用于 INPUT、FORWARD、OUTPUT 链及其子链。
- DROP
- :丢弃数据包,并且不作出回复。
- 优点:在一定程度上防御恶意的访问者。
- 如果其它主机对本机进行端口扫描,尝试访问每个端口。则 REJECT 动作会让对方立即发现端口的连通性,而 DROP 动作会导致不能立即判断端口的连通性。
- 如果发生 DDOS 攻击,则 REJECT 动作需要回复大量拒绝信息,消耗本机的资源,而 DROP 动作不消耗。
- 如果本机 DROP 掉所有 IP 发来的数据包,只允许被白名单 IP 访问,则可以对外隐藏本机的存在。
- 缺点:可能误导正常的访问者,以为网络不连通,或者丢包了,还要多次重试访问。
- RETURN
- :结束遍历当前 chain 。
- 如果当前 chain 是以
-j <target>
方式执行的子链,则 RETURN 之后会执行父链。 - 如果当前 chain 是内置 chain ,则 RETURN 之后会执行当前 chain 的默认动作,然后执行下一个规则表中的同类型 chain 。
- DNAT
- :修改数据包的目标 ip、port 。
- 只允许用于 nat 表的 PREROUTING、OUTPUT 链及其子链。
- SNAT
- :修改数据包的源 ip、port 。
- 只允许用于 nat 表的 INPUT、POSTROUTING 链及其子链。
- REDIRECT
- :将数据包重定向到当前网口的某个端口。
- 用途与 DNAT 相似,会将数据包的目标 ip、port 改成当前网口的 ip、port 。如果是本机生成的数据包,则将目标 ip 改成 127.0.0.1 。
- MASQUERADE
- :用途与 SNAT 相似,会将数据包的源 ip 改成通过 DHCP 等方式动态获取的 ip 。
- 如果不能动态获取 ip ,则应该使用 SNAT 动作。
- LOG
- :放行这个数据包,并记录一行日志。
- 建议将记录日志的 rule 放在其它 rule 之前执行,避免其它 rule 执行了 terminating target ,没来得及记录日志。
- MARK
- :给数据包添加 netfilter 标记。
- 只允许用于 mangle 表。
- ACCEPT
网口每次接收、发送一个数据包时,会按以下流程处理:
- 执行符合当前时间点的那个 chain 。
- 例如网口接收数据包时,会依次执行 PREROUTING 链、IP 路由、INPUT 链。
- 多个规则表中可能存在同一类型的 chain ,此时会按照各个表的优先级顺序,依次执行各个表中的同类型 chain 。
- 例如执行 OUTPUT 链时,先执行 raw 表中的 OUTPUT 链,然后执行 mangle 表中的 OUTPUT 链,以此类推。
- 执行每个 chain 时,会按从上到下的顺序读取每行 rule ,即逐行遍历。
- 如果某行 rule 匹配数据包,则执行该 rule 的 target 。然后:
- 如果该 rule 的 target 属于终止式目标(terminating target),包括 ACCEPT、REJECT、DROP、RETURN ,则结束遍历当前 chain ,然后执行父链或下一个规则表中的同类型 chain 。
- 如果该 rule 的 target 不属于 terminating target ,则继续读取下一行 rule 。
- 如果某行 rule 不匹配数据包,则继续读取下一行 rule 。
- 如果该 chain 中的所有 rule 都不匹配数据包,则执行该 chain 的默认动作。
- 用户创建的 chain 不支持设置默认动作。
- 默认动作是 chain 的元数据。每个 chain 的默认动作一般为 ACCEPT 。
- 用户也可以在 chain 最下方设置一行 rule ,匹配所有数据包并执行某动作,从而实现默认动作的效果。
- 如果某行 rule 匹配数据包,则执行该 rule 的 target 。然后:
- 执行符合当前时间点的那个 chain 。
# 命令
iptables
# 关于 table
-t filter # 指定规则表,默认指定 filter 表
# 关于 chain
-L [chain] # --list ,显示 chain 及其中所有 rule ,不指定 chain 则显示当前表的所有 chain
-n # 将 domain 显示成 ip 格式,将 port 从服务名显示成数字格式。否则默认会显示前一种格式
-v # 显示详细信息,比如 pkts、bytes
-x # 显示 pkts、bytes 时精确到个位。否则默认会显示成 K、M、G 等单位
--line-numbers # 显示行号(每个 chain 中,每行 rule 有一个行号,从 1 开始递增)
-S [chain] # 显示 chain 及其中所有 rule 的创建命令,相当于导出 iptables 配置
-N <chain> # --new-chain
-X [chain] # --delete-chain ,删除 chain ,但不允许删除内置 chain 、被其它 chain 引用的 chain 、包含 rule 的 chain
-E <chain> <new_chain> # --rename-chain ,重命名 chain 。引用该 chain 的其它 rule 会同步到新的 chain 名称
-F [chain] # --flush ,清空某个 chain 中的 rule ,不指定 chain 则清空所有 chain
-P <chain> <target> # --policy ,设置某个 chain 的默认动作
-Z [chain] [n] # 将 chain 中第 n 行 rule 的 pkts、bytes 计数器重置为 0 。不指定 n 则重置整个 chain ,不指定 chain 则重置整个 table
# 关于 chain 中的 rule
-A <chain> <rule> # --append ,附加一行 rule 到最后一行
-I <chain> [n] <rule> # --insert ,插入一行 rule 到第 n 行,而原来的第 n 行 rule 会变成第 n+1 行。不指定 n 则插入到第一行
-R <chain> <n> <rule> # --replace ,插入一行 rule ,替换原来的第 n 行 rule
-D <chain> <n> # --delete ,删除第 n 行 rule
-D <chain> <rule> # 删除指定内容的 rule 。这会按 --check 的方式查找 rule ,只会删除第一个找到的 rule 。没找到则报错 iptables: No chain/target/match by that name.
-C <chain> <rule> # --check ,检查 chain 中是否存在指定内容的 rule
# 这会在 chain 中逐行遍历 rule ,如果某行 rule 与指定 rule 的 match、target 都相同,则命令返回码为 0
# 如果没找到相同的 rule ,则命令返回码为 1 ,并报错 iptables: No chain/target/match by that name
# 关于 rule 中的匹配条件。每个匹配条件之前可加 ! 表示反向匹配
-p <protocal> # 数据包采用的网络协议,可以填 tcp、udp、icmp 等。默认值为 all ,表示匹配所有网络协议
-s <ip> # --source ,源地址。可以填 ip(可选带掩码)或 domain 格式。如果为 domain 格式,则会解析成 ip 再传给 netfilter ,不会动态 DNS 解析
-d <ip> # --destination ,目标地址
--sport <n> # 源端口
--dport <n> # 目的端口
-i <name> # --in-interface ,数据包从哪个网口接收。默认匹配所有网口。可以加 + 后缀作为通配符
-o <name> # --out-interface ,数据包发送到哪个网口。默认匹配所有网口
-m <match> # 指定一个扩展的匹配条件
# 关于 rule 中的动作
-j <target> # --jump ,表示 rule 对于符合匹配条件的数据包执行什么动作
# 可以填内置动作(built-in target)。
# 可以填用户创建的 chain 名称,作为子链。这会遍历子链中的每行规则,如果匹配当前数据包则执行,然后回到父链
# 可以省略该选项,此时 rule 不会对数据包产生任何影响,但依然会累计 pkts、bytes
-g <chain> # --goto ,跳转到子链,且不会回到父链
# 其它
-w [n] # --wait 。修改 iptables 配置之前会自动获取 xtables 锁,如果获取不到,则默认结束命令。使用该选项可阻塞等待 n 秒,不指定 n 则无限等待
例:查看 INPUT 链
[root@CentOS ~]# iptables -nvL INPUT --line-numbers Chain INPUT (policy ACCEPT 0 packets, 0 bytes) num pkts bytes target prot opt in out source destination 1 2819 446K ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 2 50557 383M ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED 3 0 0 ACCEPT all -- lo * 0.0.0.0/0 0.0.0.0/0 4 53 4960 DROP all -- * * 0.0.0.0/0 0.0.0.0/0 ctstate INVALID 5 484 26644 REJECT all -- * * 0.0.0.0/0 0.0.0.0/0 reject-with icmp-port-unreachable
各字段含义:
pkts # 该 rule 累计匹配的数据包数 bytes # 该 rule 累计匹配的数据包总大小 target # 该 rule 的动作 prot # 该 rule 匹配的网络协议 opt # 该 rule 的选项 in # 该 rule 匹配哪个网口接收的数据包 out # 该 rule 匹配哪个网口发送的数据包 source # 该 rule 匹配哪个源地址的数据包 destination # 该 rule 匹配哪个目标地址的数据包
例:修改 rule
iptables -N chain1 # 创建一个链 iptables -X chain1 # 删除一个链 iptables -P chain1 DROP # 设置 chain1 链的默认动作为 DROP iptables -A chain1 -p icmp -j REJECT # 在 chain1 链尾部附加一行规则:如果数据包采用 ICMP 协议,则拒绝 --reject-with icmp-port-unreachable # REJECT 动作默认会回复一个表示拒绝访问的 ICMP 数据包 --reject-with tcp-reset # 也可回复其它类型的数据包 iptables -A chain1 -p tcp --dport 80 -j ACCEPT # 如果数据包采用 TCP 协议,且目标端口为 80 ,则放行 iptables -I chain1 -p tcp -s 10.0.0.0/24 --dport 22 -j ACCEPT # 在 chain1 链头部插入一行规则:如果数据包采用 TCP 协议,且源地址、目标端口符合条件,则放行 iptables -I chain1 -m state --state RELATED,ESTABLISHED -j ACCEPT # 如果数据包关联、属于已有的网络连接,则放行。这是有状态防火墙的常见功能
- 指定多个端口的语法:
--dport 80:90 # 指定一组连续的端口 --match multiport --dport 80,443 # 指定多个端口,用逗号分隔
- 指定多个端口的语法:
用 iptables 命令修改的防火墙规则,在主机重启时会丢失。
- 为了持久保存配置,可以将 iptables 的当前配置保存到
/etc/sysconfig/iptables
文件中,每次主机重启时会自动导入该文件。iptables-save > /etc/sysconfig/iptables # 导出 -c # 增加导出每行规则的 pkts、bytes iptables-restore < /etc/sysconfig/iptables # 导入
- iptables-save 导出的配置采用特定格式,只能被 iptables-restore 识别。如果想导出可执行的命令,可执行
iptables -t filter -S
。
- 为了持久保存配置,可以将 iptables 的当前配置保存到
# nat
- 例:给 nat 表添加规则
# 采用 REDIRECT 动作。如果网口发送的数据包指向该 ip、port ,则重定向到 80 端口 iptables -t nat -A OUTPUT -p tcp -d 10.0.0.0/24 --dport 80 -j REDIRECT --to-ports 80 # 在 OUTPUT 链执行 DNAT 动作,影响本机生成并发送的数据包 iptables -t nat -A OUTPUT -p tcp -d 10.0.0.0/24 --dport 80 -j DNAT --to-destination 10.0.0.1:80 # 在 PREROUTING 链执行 DNAT 动作,影响本机接收的数据包 iptables -t nat -A PREROUTING -p tcp -d 10.0.0.0/24 --dport 80 -j DNAT --to-destination 10.0.0.1:80 # 以下规则是进行负载均衡:将 50% 的数据包转发给 10.0.0.1:80 ,剩下的数据包转发给 10.0.0.2:80 iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 10.0.0.1:80 -m statistic --mode random --probability 0.5 iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 10.0.0.2:80 # 在 POSTROUTING 链执行 SNAT 动作 iptables -t nat -A POSTROUTING -p tcp -s 10.0.0.1 -j SNAT --to-source 10.0.0.2
# log
- 例:对数据包记录日志查看日志:
iptables -t mangle -I OUTPUT -p icmp -d 127.0.0.1 -j LOG --log-level info --log-prefix '<iptables log mangle.OUTPUT> ' iptables -t mangle -I INPUT -p icmp -d 127.0.0.1 -j LOG --log-level info --log-prefix '<iptables log mangle.INPUT > '
[root@CentOS ~]# ping 127.0.0.1 -c 1 [root@CentOS ~]# tail -f /var/log/messages | grep '<iptables log' Dec 12 12:00:01 [localhost] kernel: <iptables log mangle.OUTPUT> IN= OUT=lo SRC=127.0.0.1 DST=127.0.0.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=62883 DF PROTO=ICMP TYPE=8 CODE=0 ID=10362 SEQ=1 Dec 12 12:00:01 [localhost] kernel: <iptables log mangle.INPUT > IN=lo OUT= MAC=00:00:00:00:00:00:00:00:00:00:00:00:08:00 SRC=127.0.0.1 DST=127.0.0.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=62883 DF PROTO=ICMP TYPE=8 CODE=0 ID=10362 SEQ=1 Dec 12 12:00:01 [localhost] kernel: <iptables log mangle.OUTPUT> IN= OUT=lo SRC=127.0.0.1 DST=127.0.0.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=62884 PROTO=ICMP TYPE=0 CODE=0 ID=10362 SEQ=1 Dec 12 12:00:01 [localhost] kernel: <iptables log mangle.INPUT > IN=lo OUT= MAC=00:00:00:00:00:00:00:00:00:00:00:00:08:00 SRC=127.0.0.1 DST=127.0.0.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=62884 PROTO=ICMP TYPE=0 CODE=0 ID=10362 SEQ=1
- 前两行日志表示 lo 网口发出一个 ICMP 请求包,然后被自己接收,数据包 ID 为 62883 。
- 后两行日志表示 lo 网口发出一个 ICMP 响应包,然后被自己接收,数据包 ID 为 62884 。
- 各字段的含义:
IN # 该数据包从哪个网口接收。如果数据包是本机生成并发送的,则该字段为空 OUT # 该数据包从哪个网口发出。如果数据包是被本机接收的,则该字段为空 MAC # 一串十六进制数,由三个字段拼凑而成:数据包的源 MAC 地址(12 字节)、目标 MAC 地址(12 字节)、以太网帧的类型(4 字节) SRC # 数据包的源 IP 地址 DST # 数据包的目标 IP 地址 LEN # 数据包的长度,单位为字节 TOS # Type Of Service ,服务类型 PREC # Precedence ,数据包的优先级 TTL # 数据包剩下的生存时间 ID # 数据包的 ID 。如果一个 IP 数据包分成多个片段(fragment),则这些片段拥有相同的 ID DF # Don't Fragment ,表示该数据包没有分成多个片段 MF # More Fragments following ,表示该数据包分成了多个片段 SEQ # 数据包的序列号。例如 ping 命令发送多个 ICMP 包时,序列号从 1 开始递增
# ipset
如果需要对多个 ip 执行相同的防火墙规则,例如:
iptables -I INPUT -p tcp -s 10.0.0.1 -j ACCEPT iptables -I INPUT -p tcp -s 10.0.0.2 -j ACCEPT iptables -I INPUT -p tcp -s 10.0.0.3 -j ACCEPT
则可用 ipset 命令创建一个哈希集合,包含多个 ip :
ipset create ipset1 hash:net ipset add ipset1 10.0.0.1 ipset add ipset1 10.0.0.2 ipset add ipset1 10.0.0.3 iptables -I INPUT -p tcp -m set --match-set ipset1 src -j ACCEPT
优点:
- 方便管理多个 ip ,简化防火墙规则。
- 使用哈希集合时,比线性查找的效果更高。
ipset 命令的语法:
ipset list [set] # 列出所有集合的信息,或者指定一个集合 ipset create <set> <type> # 创建一个集合 ipset destroy <set> # 删除一个集合 ipset add <set> <member> # 在集合中添加元素 ipset del <set> <member> # 在集合中删除元素 ipset save > /etc/ipset.conf # 导出配置 ipset restore -! < /etc/ipset.conf # 导入配置,并忽略重复项
# nftables
相关历史:
- 2008 年,netfilter 开发团队公布了 nftables 项目,它是一个基于 netfilter 的网络框架,旨在代替 xtables 工具集与 netfilter 交互,从而提高效率。
- 之前的 Linux 中,防火墙前端为 xtables 工具集,后端为 netfilter 。
- 改用 nftables 时,前端为 nft 命令,后端为 nftables 。而 nftables 的后端为 netfilter 。
- 不过大部分用户依然更习惯使用 xtables 工具集。
- 2014 年,Linux 3.13 内核发布,添加了 nftables 框架,包括内核模块 nf_tables 等。
- 2008 年,netfilter 开发团队公布了 nftables 项目,它是一个基于 netfilter 的网络框架,旨在代替 xtables 工具集与 netfilter 交互,从而提高效率。
与 xtables 相比,nftables 的优点:
命令语法简单了些,但依然复杂。
采用链表结构,因此修改更快。
- xtables 命令每次修改防火墙规则时,需要从内核获取全部规则,是一块二进制 blob 。在用户态修改之后,再全部传给内核。因此规则越多,耗时越久,时间复杂度为 O(n) 。
- 而 nftables 采用链表结构存储所有规则,修改规则的时间复杂度为 O(1) 。
- 例如 k8s 中用 iptables 配置了大量防火墙规则,还需要经常修改,明显耗时更久。
不需要获取锁,因此修改更快。
- 为了避免同时执行多个 xtables 进程时发生冲突。每个 xtables 进程在修改防火墙规则之前,需要先获取
/run/xtables.lock
这个文件锁。如果未获取到锁,则阻塞等待。 - 而 nftables 不需要获取锁。
- 为了避免同时执行多个 xtables 进程时发生冲突。每个 xtables 进程在修改防火墙规则之前,需要先获取
减少了规则表、规则链,因此查找更快。
- iptables 内置了 5 个规则表,每个表中内置了多个规则链。即使这些规则链为空,处理数据包时也需要读取这些规则链,增加了轻微耗时。
- 而 nftables 只内置了 filter 表。
原生支持创建哈希集合,使得部分查找更快。
- iptables 原生不支持创建哈希集合。可用 ipset 命令创建 ip 的集合,但不支持创建 port 的集合。
- 而 nftables 原生支持创建哈希集合,例如:
# 创建一个集合,包含多个 ip nft add element ip filter ipset1 {10.0.0.1,10.0.0.2} nft add rule ip filter output ip daddr != @ipset1 accept # 创建一个集合,包含多个 port nft add rule ip filter input tcp dport {80,443} accept
- nftables 还支持创建 map 映射表,例如:
# 将访问 81、82 端口的数据包,分别 DNAT 转发到不同 ip nft add rule ip nat prerouting dnat to tcp dport map {81:10.0.0.1,82:10.0.0.2}
- 总之:
- xtables 每次处理数据包时需要遍历所有防火墙规则,找到与当前数据包匹配的规则,属于线性查找,时间复杂度为 O(n) 。规则越多,查找的耗时越久,单位时间内收发数据包的吞吐量越低。
- nftables 基本上也是线性查找,但部分查找可用哈希集合加速,让时间复杂度变为 O(1) 。
- 例如 k8s 中,经常会对多个 ip 执行相同防火墙规则,因此用哈希集合能大幅提高查找速度。
默认未启用计数器,降低了少量开销。
- iptables 对每个 chain 和 rule 启用了 pkts、bytes 计数器,且不能禁用。
- 而 nftables 默认未启用计数器,需要用户主动启用。
配置针对 ipv6 的规则时,更方便。
- iptables 命令只能处理 ipv4 流量,用户需要将 iptables 配置的防火墙规则同步到 ip6tables 命令,才能处理 ipv6 流量。
- 而 nft 命令可以创建一个 family 为 inet 的 table ,同时处理 ipv4、ipv6 流量。
升级软件版本更容易。
- xtables 的大部分代码集成在 Linux 内核中,修改代码时需要重新编译内核,因此难以修改。
- 而 nftables 的大部分代码在 Linux 内核之外,工作在用户态,因此便于升级 nftables 的软件版本。
用户可以同时使用 iptables (后端为 netfilter )和 firewall-cmd (后端为 nftables )两个命令来配置防火墙规则,但它们配置的规则冲突时,优先级不明。因此建议不要同时使用,而是采用以下做法之一:
- 只用 iptables 命令配置防火墙,停止或卸载 firewalld 进程。
- 只用 firewalld-cmd 命令配置防火墙,使用 iptables 命令时仅涉及读取操作。
# 命令
- nftables 提供了一个 nft 命令来配置防火墙规则。命令语法:
nft list tables # 显示所有 table nft list table [family] <table> # 只显示指定的 table nft add table [family] <table> nft delete table [family] <table> nft list chains # 显示所有 table ,以及每个 table 中的所有 chain ,每个 chain 中的所有 rule nft list chain [family] <table> <chain> nft add chain [family] <table> <chain> [priority] nft delete chain [family] <table> <chain> nft add rule [family] <table> <chain> statement [comment] nft delete rule [family] <table> <chain> statement
- family 是指网络协议栈,可以取值为 ip、ip6、inet、arp 等。
- 不指定 family 时,默认值为 ip ,表示 ipv4 。
- inet 是指 ipv4、ipv6 的合集。
- 每个 table 只能处理一种 family 的流量。
- priority 是指优先级,从高到低排列如下:
raw mangle dstnat filter security srcnat
- 例如:相当于:
nft add rule ip filter output ip daddr 1.2.3.4 drop
iptables -A OUTPUT -d 1.2.3.4 -j DROP
- family 是指网络协议栈,可以取值为 ip、ip6、inet、arp 等。
# 迁移
- 为了方便用户从 xtables 迁移到 nftables ,nftables 提供了 xtables-nft 工具集,包含多个命令行工具:
iptables-nft iptables-nft-save iptables-nft-restore ip6tables-nft ip6tables-nft-save ip6tables-nft-restore arptables-nft ebtables-nft
- 这些命令的语法兼容 xtables 工具集,但配置的防火墙规则会传给 nftables ,而不是 netfilter 。
- nftables 还提供了以下命令,用于将 xtables 语法的命令,转换成 nft 语法的命令。例:
iptables-translate ip6tables-translate iptables-restore-translate -f <file> # 读取 iptables 导出的配置文件,翻译并打印到终端 ip6tables-restore-translate -f <file>
[root@CentOS ~]# iptables-translate -t filter -A chain1 -p tcp --dport 80 -j ACCEPT nft add rule ip filter chain1 tcp dport 80 counter accept
- 用户可执行 xtables-nft-multi 命令,以子命令的方式调用上述命令,例如:
xtables-nft-multi iptables-nft -L
- RHEL 8 发行版弃用了 xtables 工具集。当用户执行 iptables 命令时,实际上是执行 iptables-nft 命令。用户可执行以下命令,检查后端是否为 nf_tables :
[root@CentOS ~]# iptables -V iptables v1.8.4 (nf_tables)
# firewalld
firewalld 是 Red Hat 公司开发的一款防火墙软件。
- GitHub (opens new window)
- 于 2011 年发布,采用 Python 语言开发。
- firewalld 防火墙的前端命令为 firewalld-cmd 。后端默认为 nftables ,也可改为 netfilter 。
- RHEL 7 发行版中,默认的防火墙软件从 iptables 改为了 firewalld 。
优点:
- 简化了配置防火墙的工作量。
缺点:
- 只是擅长配置一些常用的防火墙规则,不如 nft、iptables 的功能多。
使用 firewalld 的步骤:
- 安装并运行 firewalld 进程。
yum install firewalld systemctl start firewalld systemctl enable firewalld
- 用 firewalld-cmd 命令配置防火墙规则。
- 安装并运行 firewalld 进程。
# zone
firewalld 引入了区域(zone)的设定,每个 zone 包含一组防火墙规则。
- 同时只能启用一个 zone 。
- 用户可以在各个 zone 中配置不同的防火墙规则,然后通过切换 zone 来快速切换防火墙规则。
- 各个 zone 的配置文件保存在
/etc/firewalld/zones/
目录下,采用 XML 文件格式。
常用的 zone 举例:
- trusted :允许接收所有数据包。
- home 、internal :只允许接收 ssh、mdns、samba-client、dhcpv6-client 服务的数据包。
- work 、public :只允许接收 ssh、dhcpv6-client 服务的数据包。
- external 、dmz :只允许接收 ssh 服务的数据包。
- block 、drop :不接收外部发来的数据包。
所有的 zone 默认都允许本机发送数据包到任何目标地址,允许接收本机发送的数据包,只是不一定接收外部发送的数据包。
# 命令
firewall-cmd
--get-zones # 显示所有 zone 的名称
--get-active-zones # 显示当前启用的 zone
--get-default-zone # 显示默认的 zone
--set-default-zone=public # 设置默认的 zone
--get-zone-of-interface=eth0 # 显示指定网口采用的 zone
--change-interface=eth0 --zone=public # 修改指定网口采用的 zone
[--zone=public] # 指定一个 zone 。不指定则选择默认的 zone
--list-all # 显示 zone 内的防火墙规则
--add-service=http # 启用 http 服务,即接收发向本机 TCP 80 端口的数据包
--remove-service=http # 禁用 https 服务,即拒绝发向本机 TCP 443 端口的数据包
--add-port=80/tcp # 接收发向本机 TCP 80 端口的数据包
--add-port=80-82/tcp # 开放一组端口
--remove-port=80/tcp # 拒绝接收
--permanent # 将配置写入配置文件(从而会永久保存,但是需要 reload 或重启才会影响运行时配置)
--runtime-to-permanent # 将运行时的全部配置写入配置文件
--reload # 重新加载配置文件,这会覆盖运行时的全部配置
--complete-reload # 完全重新加载,这会重置 nf_conntrack 状态信息,中断所有网络连接
--panic-on # 开启 panic 模式,拒绝接收所有数据包
--panic-off # 关闭 panic 模式
--query-panic # 检查是否开启了 panic 模式
使用 --add-service 的效果与 --add-port 一样,只是标明了开放的端口是被哪个服务使用。
例:
[root@CentOS ~]# firewall-cmd --list-all public (active) target: default # 处理数据包的默认动作 icmp-block-inversion: no interfaces: eth0 # 只处理该网口收发的数据包 sources: services: ssh dhcpv6-client # 允许接收哪些服务的数据包 ports: 3000/tcp protocols: masquerade: yes forward-ports: port=80:proto=tcp:toaddr=10.0.0.2 # 端口转发的规则 source-ports: icmp-blocks: rich rules:
通过 rich-rule 可以配置功能更丰富的防火墙规则,如下:
firewall-cmd [--zone=public] --add-rich-rule='rule family=ipv4 port port=22 protocol=tcp accept' --add-rich-rule='rule family=ipv4 source address=10.0.0.2 port port=80 protocol=tcp accept' --remove-rich-rule='...'
- 指定 IP 地址时,可以加上子网掩码,比如 "10.0.0.1/32" 。
# 端口转发
端口转发(port forwarding)是一种高级的 NAT 功能,将本机某个端口接收的数据包转发到指定 ip、port ,相当于 Nginx 的反向代理。
- 假设让本机的某个端口反向代理主机 A 的某个端口,供主机 B 访问。工作原理如下:
- 当主机 B 发送数据包到本机端口时,会执行 DNAT ,将数据包的 dst_ip 改为主机 A 、src_ip 改为本机。然后将数据包转发给主机 A 。
- 如果只执行 DNAT 修改数据包的 dst_ip ,则数据包的 src_ip 依然是主机 B ,因此主机 A 回复数据包时会发送给主机 B ,而不是本机。
- 当主机 A 回复数据包到本机时,会执行 SNAT ,将数据包的 dst_ip 改为主机 B 、src_ip 改为本机。然后将数据包转发给主机 B 。
- 当主机 B 发送数据包到本机端口时,会执行 DNAT ,将数据包的 dst_ip 改为主机 A 、src_ip 改为本机。然后将数据包转发给主机 A 。
- 端口转发是防火墙的操作,发生在 Socket 接收数据包之前。假设一个进程监听 TCP 80 端口,则可以将 80 端口收到的所有数据包转发到别处,不传给该进程。
- 端口转发的 dst_ip ,可以是当前网口,或者其它网口、其它主机。但后者需要满足以下条件:
- 开启内核参数,允许跨网口转发数据包:
sysctl net.ipv4.ip_forward=1 echo 'net.ipv4.ip_forward=1' >> /etc/sysctl.conf
- 用 firewall-cmd 时,需要开启 masquerade 伪装功能,在转发数据包时保留 src_ip ,否则会使用本机 IP 作为 src_ip 。该功能与 SNAT 相似。
- 开启内核参数,允许跨网口转发数据包:
- 假设让本机的某个端口反向代理主机 A 的某个端口,供主机 B 访问。工作原理如下:
如果用 iptables 命令实现端口转发功能,则需要配置多条规则。例如:
# 将本机 10.0.0.1:80 端口接收的数据包,转发到 10.0.0.2:22 iptables -t nat -A PREROUTING -p tcp -d 10.0.0.1 --dport 80 -j DNAT --to-destination 10.0.0.2:22 # PREROUTING 链只影响本机接收的数据包,而 OUTPUT 链影响本机生成并发送的数据包 iptables -t nat -A OUTPUT -p tcp -d 10.0.0.1 --dport 80 -j DNAT --to-destination 10.0.0.2:22 # 转发的同时,将数据包的 src_ip 改为本机 ip iptables -t nat -A POSTROUTING -p tcp -d 10.0.0.2 --dport 22 -j SNAT --to-source 10.0.0.1
如果用 firewall-cmd 实现端口转发功能,则只需配置一条规则。例如:
firewall-cmd [--zone=public] --add-forward-port=port=80:proto=tcp:toport=22 # 将本机 TCP 80 端口收到的数据包转发到 22 端口 --add-forward-port=port=80:proto=tcp:toaddr=10.0.0.2 # 将 TCP 80 端口收到的数据包转发到另一个主机的相同端口 --add-forward-port=port=80:proto=tcp:toport=80:toaddr=10.0.0.2 # 将 TCP 80 端口收到的数据包转发到另一个主机的 80 端口 --remove-forward-port=... # 取消端口转发 --add-masquerade # 开启伪装 --remove-masquerade # 取消伪装 --add-rich-rule="rule family=ipv4 source address=10.0.0.3 forward-port port=80 protocol=tcp to-port=80 to-addr=10.0.0.2" # 允许接收数据包,并进行端口转发
- 缺点:只能影响本机接收的数据包,不能影响本机生成并发送的数据包。因为 firewall-cmd 的端口转发,仅相当于 iptables 的 nat 表的 PREROUTING 链,没有实现 OUTPUT 链的功能。为了解决该问题,可添加一条 iptables 语法的规则:
firewall-cmd --direct --add-rule ipv4 nat OUTPUT 0 -p tcp -d 10.0.0.1 --dport 80 -j DNAT --to-destination 10.0.0.2:22
- 缺点:只能影响本机接收的数据包,不能影响本机生成并发送的数据包。因为 firewall-cmd 的端口转发,仅相当于 iptables 的 nat 表的 PREROUTING 链,没有实现 OUTPUT 链的功能。为了解决该问题,可添加一条 iptables 语法的规则: