# 防火墙
# netfilter
netfilter 是 Linux 内核的一个网络框架,提供了数据包过滤(包括无状态包、有状态包)、NAT 等功能。
- 可执行
sysctl -a | grep net.netfilter
查看相关内核参数。
- 可执行
netfilter 工作在内核态,提供了以下命令供用户配置防火墙规则:
- iptables :用于过滤 IPv4 协议的数据包。
- ip6tables :用于过滤 IPv6 协议的数据包。
- arptables
- ebtables
netfilter 支持在 Linux 网络协议栈中注册一些钩子函数(hook),当内核接收、发出每个网络数据包时,就会触发 hook ,据此处理网络包。
- 用户用 iptables 命令添加的防火墙规则,会被转换成 hook 函数,注册到 netfilter 。
- hook 函数可以注册在 PRE_ROUTING、POST_ROUTING 等位置。
相关历史:
- 1998 年,Rusty Russell 创建了 netfilter 项目,旨在改进 Linux 防火墙软件。
- 2000 年,Linux 2.4 内核发布,加入了 netfilter ,后来成为了很多 Linux 发行版的默认防火墙。
- 2008 年,netfilter 开发团队公布了 nftables 项目,它是一个新的包过滤框架,提供了一个新命令 nft 来取代 iptables、ip6tables 等命令。
- 2014 年,Linux 3.13 内核发布,加入了 nftables 。
# nf_conntrack
netfilter 使用一个哈希表 nf_conntrack 记录本机所有网络连接(包括 TCP、UDP、ICMP 协议),跟踪它们的状态信息。
- 当哈希表记录满时,新建立的 TCP 连接会被防火墙拒绝,可执行
grep 'nf_conntrack: table full, dropping packet' /var/log/messages
查看已拒绝的日志。 - 如果一个网络连接一直没有传输数据、保持空闲超过 nf_conntrack_xx_timeout_xx 时长,则 nf_conntrack 会删除其记录。
- 即使网络连接已关闭,也要等超时之后才删除记录,从而能跟踪网络会话信息,实现状态防火墙或 NAT 。
- 减少 nf_conntrack_xx_timeout_xx 超时时间,能更快地删除无用的记录,节约 nf_conntrack 的容量。
- 当哈希表记录满时,新建立的 TCP 连接会被防火墙拒绝,可执行
例:
- 如果 client 与 server 建立 TCP 连接,则双方都有一个 ESTABLISHED 状态的 Socket 、在 nf_conntrack 中记录一个 ESTABLISHED 状态的 TCP 连接。
- 如果 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 状态。
- 如果执行 telnet 命令,连接到某个主机的 TCP 端口,发送多个 TCP 包,则 nf_conntrack 只会记录一条 TCP 连接。
- 每发送一个新包,就会刷新一次 nf_conntrack 记录,重新计算还剩多少秒可删除。
- 如果未成功建立网络连接,比如被防火墙拒绝了,则不会被 nf_conntrack 记录。
- 如果执行一次 ping 命令,向目标 IP 发送多个 ICMP 包,则 nf_conntrack 只会记录一条。
- 如果执行多次 ping 命令,即使目标 IP 相同, nf_conntrack 也会分别记录一条。
相关命令:
cat /proc/net/nf_conntrack # 查看哈希表的当前内容 cat /proc/sys/net/netfilter/nf_conntrack_count # 查看哈希表当前记录的连接数
例:
[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 。
# iptables
在 RHEL 7 之前的 RHEL 发行版中,默认使用 iptables 命令来管理防火墙。
iptables 默认定义了四个规则表,
- raw
- mangle
- nat :用于端口转发、NAT 地址转换。
- filter :用于定义流量出入规则,过滤数据包。
所有网络数据包会按
raw > mangle > nat > filter
的顺序依次被四个规则表处理。- 每个规则表有 INPUT、OUTPUT 等几个基础规则链,按特定顺序执行。
- 用户可以创建其它规则链,然后在基础规则链里添加规则,将某些数据包交给自定义的规则链处理。
- 每个规则链可以添加任意条规则。
- 执行每个规则链时,会从上到下检查每条规则。
- 如果有一条规则匹配当前数据包,则立即执行,然后检查下一条规则。
- 如果没有遇到匹配的规则,则执行该规则表的默认规则。
- 每个规则表有 INPUT、OUTPUT 等几个基础规则链,按特定顺序执行。
几个基础规则链:
- INPUT :处理被网卡接收的数据包。
- OUTPUT :处理从网卡发出的数据包。
- PREROUTING :处理被网卡接收的数据包,在 IP 路由之前处理,比如修改目标 IP 。
- POSTROUTING :处理从网卡发出的数据包,在 IP 路由之后处理,比如修改源 IP 。
- FORWARD :处理路由转发的数据包。这些数据包的目标 IP 不是本机网卡,因此需要路由转发到其它主机。
- 网卡收到数据包时,会先后经过 PREROUTING 规则链、IP 路由、INPUT 规则链处理,然后写入 Socket ,供应用程序读取。
- 应用程序将数据包写入 Socket 时,会先后经过 OUTPUT 规则链、IP 路由、POSTROUTING 规则链处理,然后由网卡发送出去。
- 网卡收到需要路由转发的数据包时,会先后经过 PREROUTING 规则链、IP 路由、POSTROUTING 规则链处理,然后由网卡发送出去。
常见的几种动作(target):
- ACCEPT
- :允许数据包通过。
- REJECT
- :拒绝数据包通过,并回复一条拒绝信息给发送方。
- 不能用作默认动作。
- DROP
- :丢弃数据包,并且不作出回复。这可能导致发送方误以为网络不通。
- LOG
- :记录日志。
- 日志规则应该放在其它规则之前。因为执行日志规则之后还会执行后续规则,但执行其它动作之后就会结束规则链。
- DNAT
- :修改数据包的目标 IP、PORT 。
- 只能用于 nat 表的 OUTPUT 或 PREROUTING 链。
- SNAT
- :修改数据包的源 IP、PORT 。
- 只能用于 nat 表的 INPUT 或 POSTROUTING 链。
- REDIRECT :将数据包重定向到本机的某个端口。
- 与 DNAT 动作类似,会修改数据包的目标地址,但还会记录旧的目标地址,便于实现代理转发。
- 另外,mangle 表的 TPROXY 动作可将数据包重定向到本机某个 Socket ,且不修改数据包的目标地址。
- ACCEPT
# 安装
yum install iptables-services
systemctl start iptables
systemctl enable iptables
# 命令
$ iptables
-L [chain] # 显示所有规则,或只显示某个规则链
-n # 将服务名显示成具体的端口号
-v # 显示详细的信息
--line-numbers # 显示行号
-I <chain> [n] # 编辑某个规则链,插入一条新规则到第 n 行,默认是第一行
-A <chain> # 编辑某个规则链,添加一条新规则到最后一行
-D <chain> <n> # 删除规则链的第 n 行
-F [chain] # 清空某个规则链,不指定 chain 则清空所有规则链
-P <chain> <target> # 设置某个规则链的默认动作
-t filter # 指定表,默认指定 filter 表
-p <protocal> # 指定协议
-s <ip> # 指定源地址
-d <ip> # 指定目的地址
--sport <n> # 指定源端口
--dport <n> # 指定目的端口
-j <target> # 指定动作
-i eth0 # input ,指定接收流量的网口
-o eth0 # ouput ,指定发出流量的网口
例:给 filter 表添加规则
iptables -D INPUT # 删除 INPUT 规则链 iptables -P INPUT DROP # 设置 INPUT 规则链的默认动作为 DROP iptables -I INPUT -p icmp -j REJECT # 在 INPUT 规则链的开头插入一条新规则,拒绝接收 icmp 数据包 iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT # 允许接收本机发起的连接的响应 iptables -A INPUT -p tcp --dport 80 -j ACCEPT # 允许所有 IP 地址发送 tcp 数据包到 80 端口 iptables -I INPUT -p tcp -s 10.0.0.0/24 --dport 22 -j ACCEPT # 允许指定 IP 地址网段发送 tcp 数据包到 22 端口
- 可以重复添加同样的规则。
- 指定多个端口的语法:
--dport 80:90 # 指定一组连续的端口 --match multiport --dport 80,90 # 指定多个端口,用逗号分隔
例:查看详细的 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-host-prohibited
各字段含义:
pkts # 该规则已匹配的数据包数 bytes # 该规则已匹配的数据包总大小 target # 该规则采取的动作 prot # 该规则针对的协议 opt # 该规则的选项 in # 该规则针对从哪个网口接收的数据包 out # 该规则针对向哪个网口发出的数据包 source # 该规则针对哪个源地址的数据包 destination # 该规则针对哪个目的地址的数据包
例:给 nat 表添加规则
iptables -t nat -A OUTPUT -p tcp -d 10.0.0.0/24 --dport 80 -j REDIRECT --to-ports 80 # 采用 REDIRECT 动作,将发向指定目标 IP、PORT 的流量,重定向到本机的 80 端口 iptables -t nat -A OUTPUT -p tcp -d 10.0.0.0/24 --dport 80 -j DNAT --to-destination 10.0.0.1:80 # 采用 DNAT 动作 iptables -t nat -A POSTROUTING -p tcp -s 10.0.0.1 -j SNAT --to-source 1.1.1.1 # 采用 SNAT 动作
例:记录日志
iptables -t nat -I OUTPUT -d 10.0.0.0/24 -j LOG --log-level info --log-prefix '<iptables log>' tail -f /var/log/messages | grep '<iptables log>'
执行
service iptables save
会将 iptables 的当前配置保存到 /etc/sysconfig/iptables 文件中,每次系统重启时会自动读取。- 可以手动导入、导出 iptables 的配置:
iptables-save > iptables.conf # 导出到一个文件 iptables-restore < iptables.conf # 从文件中导入
- 可以手动导入、导出 iptables 的配置:
# firewalld
firewalld 是 Red Hat 公司开发的防火墙软件。
- 于 2011 年发布,采用 Python 开发。
- 默认后端为 nftables ,也兼容 netfilter 。
- 在 RHEL 7 发行版中, firewalld 取代 iptables 成为了默认防火墙软件。
- 用户可以同时使用 iptables 和 firewalld 两个防火墙软件,但容易搞混规则。建议将一个防火墙关闭或默认允许所有流量,只使用另一个防火墙。
使用 firewalld 的步骤:
- 用户先启动 firewalld 守护进程。
- 然后执行 firewalld-cmd 命令配置防火墙规则。
firewalld 引入了区域(zone)的设定,用户可以在各个 zone 中分别配置防火墙规则,然后通过切换 zone 来快速切换防火墙规则。常用的 zone 如下:
- trusted :允许接收所有数据包。
- home 、internal :只允许接收 ssh mdns samba-client dhcpv6-client 服务的数据包。
- work 、public :只允许接收 ssh、dhcpv6-client 服务的数据包。
- external 、dmz :只允许接收 ssh 服务的数据包。
- block 、drop :不接收外部发来的数据包。
- 所有的 zone 默认都允许发出数据包、接收回复本机的数据包。
firewalld 各个区域的配置文件以 XML 文件的形式保存在
/etc/firewalld/zones/
目录下。
# 安装
yum install firewalld
systemctl start firewalld
systemctl enable firewalld
# 命令
firewall-cmd
--get-zones # 显示所有区域的名字
--get-active-zones # 显示当前激活的区域
--get-default-zone # 显示默认区域的名称
--set-default-zone=public # 设置默认区域
--get-zone-of-interface=eth0 # 显示指定网口采用的区域
--change-interface=eth0 --zone=public # 修改指定网口采用的区域
[--zone=public] # 选择指定区域进行配置,不声明则是选择默认区域
--list-all # 显示区域的信息
--add-service=http # 允许接收 http 服务(即 TCP 80 端口)的数据包
--remove-service=http # 拒绝接收
--add-port=80/tcp # 允许接收发送到 TCP 80 端口的数据包
--add-port=80-82/tcp # 开放一组端口
--remove-port=80/tcp # 拒绝接收
--config_something... --permanent # 将配置写入配置文件(从而会永久保存,但是需要重启或 reload 才会生效)
--runtime-to-permanent # 将运行时的所有配置写入配置文件
--reload # 重新加载 firewall 的配置文件(这会覆盖运行时的配置)
--complete-reload # 完全重新加载(这会丢失所有运行时的配置)
--panic-on # 开启 panic 模式,拒绝接收所有数据包
--panic-off # 关闭 panic 模式
--query-panic # 显示是否开启了 panic 模式
使用 --add-service 的效果与 --add-port 一样,只是标明了开放的端口是被哪个服务使用。
指定 IP 地址时,可以加上子网掩码,比如 "10.0.0.1/32" 。
例:
[root@CentOS ~]# firewall-cmd --list-all public (active) target: default # 处理数据包的默认动作(比如 ACCEPT、DROP) 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='...'
# 端口转发
- 防火墙能够将本机网口某个端口收到的数据包转发到另一个端口,甚至是其它网口的端口。
- 防火墙的端口转发在内核态工作,比 Socket 监听端口的优先级更高。
命令:
firewall-cmd [--zone=public]
--add-forward-port=port=80:proto=tcp:toport=8080 # 将本机 TCP 80 端口收到的数据包转发到 8080 端口
--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" # 允许接收数据包,并进行端口转发
- 端口转发时不需要本机有进程监听该端口。
- 只能转发外部发给本机的数据包,不能转发本机内部发出的数据包。不如 iptables 的 NAT 功能。
- 跨网口转发数据包时,需要以下条件:
- 启用内核参数:
echo 1 > /proc/sys/net/ipv4/ip_forward
- 开启 masquerade 伪装功能,在转发数据包时保留源 IP ,否则会使用本机 IP 作为源 IP 。该功能与 SNAT 相似。
- 启用内核参数:
# 相关概念
- 有状态防火墙(stateful firewall)
- :能记录数据包的上下文信息,据此过滤数据包。
- 与之相比,无状态防火墙只能根据源地址、目标地址等信息过滤数据包。
- Linux 的 iptables、firewalld 都属于有状态防火墙。
- 假设一个主机监听 TCP 80 端口,并让有状态防火墙放通 TCP 80 端口。然后另一个主机连接到该端口,建立 TCP 连接。
- 有状态防火墙会识别出哪些 TCP 网络包属于该会话,放通这些网络包。即使让有状态防火墙关闭 TCP 80 端口,也不会中断该 TCP 连接,双方能继续通信。
- UDP 是无连接的,因此有状态防火墙关闭端口之后,就不能继续通信。