# 防火墙

# 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 的容量。
  • 例:

    • 如果 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 :处理从网卡发出的数据包。
    • 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 ,且不修改数据包的目标地址。

# 安装

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    # 从文件中导入
      

# firewalld

  • firewalld 是 Red Hat 公司开发的防火墙软件。

    • 于 2011 年发布,采用 Python 开发。
    • 默认后端为 nftables ,也兼容 netfilter 。
    • 在 RHEL 7 发行版中, firewalld 取代 iptables 成为了默认防火墙软件。
      • 用户可以同时使用 iptables 和 firewalld 两个防火墙软件,但容易搞混规则。建议将一个防火墙关闭或默认允许所有流量,只使用另一个防火墙。
  • 使用 firewalld 的步骤:

    1. 用户先启动 firewalld 守护进程。
    2. 然后执行 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 是无连接的,因此有状态防火墙关闭端口之后,就不能继续通信。