iptables 服务使用说明

iptables 的底层实现是 netfilter,netfilter 的架构是在整个网络流程的若干位置放置一些钩子,并在每个钩子上挂载一些处理函数进行处理。

IP 层的 5 个钩子点的位置,对应就是 iptables 的 5 条内置链,分别是

  • PREROUTING
  • FORWARD
  • INPUT
  • OUTPUT
  • POSTROUTING


当网卡收到一个网络报文送达协议栈时,最先经过的 netfilter 钩子是 PREROUTING,此处常见的钩子函数是 目的地址转换 (DNAT)。无论 PREROUTING 是否存在钩子处理网络数据包,下一步内核都会通过查询本地路由表决定这个数据包的流向

  • 如果是发送给本地进程,则进入 INPUT 链传给本地进程
  • 如果是发送给其他机器(或者其他 network namespace),则经过 netfilter 的 FORWARD 钩子传送出去,相当于将本地机器当作路由器

所有马上要发送到网络协议栈之外的数据包,都会经过 POSTROUTING 钩子,这里常见的处理函数是 源地址转换(SNAT) 或者 源地址伪装(Masquerade, 简称 Masq)

除了 5 条内置的链,iptables 还有 5 张表,这 5 张表主要是用来给 iptables 中的规则(rule)分类,系统中所有的 iptables 规则都被划分到不同的表集合中。5 张表分别为

  • raw - iptables 是有状态的,即 iptables 对数据包有连接追踪 (connection trackong) 机制,而 raw 可以用来去除这种追踪机制
  • mangle - 用于修改数据包的 IP 头信息
  • nat - 用于修改数据包的源或者目的地址
  • filter - 用于控制到达某条链上面的数据包是继续放行、直接丢弃(drop)、或拒绝(reject)
  • security - 用于在数据包上面应用 SELinux

表是有优先级的,5 张表的优先级从高到低是: rawmanglenatfiltersecurity,iptables 不支持自定义表。不是每个链上都能挂表,iptables 表与链的对应关系如下图

- PREROUTING FORWARD INPUT OUTPUT POSTROUTING
raw Y N N Y N
mangle Y Y Y Y Y
nat (SNAT) N N Y N Y
nat (DNAT) Y N N Y N
filter N Y Y Y N
security N Y Y Y N

iptables 表和链的工作流程图如下

iptables 命令

常用选项说明

选项 说明 示例
-F ,--flush 清除所有规则,默认规则除外
-P ,--policy 设置默认规则
-t ,--table 指定要操作的表,默认为 filter iptables -t nat -P INPUT ACCEPT
--list ,-L [chain [rulenum]] 列出(指定的链或所有链)的规则 iptables -t nat -L -v -n --line-numbers
--verbose ,-v verbose mode
--numeric ,-n 不解析协议和端口号,以数字的形式显示
--line-numbers 显示规则的行号,可以根据行号对具体的规则进行操作
--jump ,-j 匹配的规则的处理 target iptables -A INPUT -j LOG
--append ,-A chain 像指定的链中追加规则 -A INPUT -i lo -j ACCEPT
--insert ,-I chain [rulenum] 向指定的链中指定的位置插入规则 iptables -I INPUT 10 -p tcp --dport 80 -j ACCEPT
--delete ,-D chain rulenum 删除指定链中的指定位置的规则 iptables -D INPUT 10
--replace ,-R chain rulenum 更新指定链中的指定位置的规则
-S, --list-rules [chain] 按照类似 iptables-save 的输出打印规则

删除指定规则

查看规则及行号

$ iptables -L -v -n --line-number
Chain INPUT (policy DROP 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 90 6516 ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
2 0 0 ACCEPT all -- lo * 0.0.0.0/0 0.0.0.0/0
3 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 state NEW tcp multiport dports 10050,10051

删除指定行号的规则

iptables -D INPUT 7

记录 iptables 日志

可以通过以下 2 种方式之一查看 iptables 记录的日志

rsyslog 服务记录日志

  • 使用 rsyslog 服务记录日志

    rsyslog.conf 中添加配置 kern.* /var/log/iptables.log

    重启 rsyslog 服务

    systemctl restart rsyslog.service
  • journalctl -k 命令查看

    journalctl -f -k | grep iptables 

配置 iptables 记录日志

iptables 配置日志选项:

# 记录所有通过防火墙的日志,之后可以在 /var/log/messages 中查看日志
iptables -A INPUT -j LOG --log-prefix "iptables"
# 只记录特定日志的方法
iptables -A INPUT -s 192.168.10.0/24 -p tcp -j LOG --log-prefix "iptables icmp warn"

iptables 中日志相关的选项:

选项 说明 示例
--log-prefix 记录的日志的内容前缀 -A INPUT -j LOG --log-prefix "iptables"
--log-level 日志级别
由高到低 emergalertcriterrorwarningnoticeinfodebug
-A INPUT -s 192.168.10.0/24 -j LOG --log-level 4

使用 LOG 动作,可以将符合条件的报文的相关信息记录到日志中,但当前报文具体是被“接受”,还是被“拒绝”,都由后面的规则控制,换句话说,LOG 动作只负责记录匹配到的报文的相关信息,不负责对报文的其他处理,如果想要对报文进行进一步的处理,可以在之后设置具体规则,进行进一步的处理。

端口转发

以下示例实现,将目标端口为 80 的流量转发到目标端口 8080

iptables -t nat -A PREROUTING -s 192.168.10.0/24 -p tcp --dport 80 -i eth0 -j REDIRECT --to-port 8080

NAT

DNAT

DNAT 根据指定条件修改数据包的目标 ip 地址和目标端口,DNAT 的原理和端口转发的原理差不多,区别是端口转发不修改 ip 地址。需要注意的是,当转发的 ip 地址不是本机时,需要确保启用 ip froward 功能(echo 1 > /proc/sys/net/ipv4/ip_forward),即把 Linux 当做交换机使用

iptables -t nat -A PREROUTING -d 192.168.10.10 -p tcp --dport 80 -j DNAT --to-destination 192.168.10.20:8080

SNAT 和 Maskuerade

Maskuerade 本质上是 SNAT 的一种。SNAT 根据指定的条件修改数据包的源 IP 地址,SNAT 策略只能发生在 nat 表的 POSTROUTING 链

iptables -t nat -A POSTROUTING -s 192.168.10.10 -j SNAT --to-source 192.168.10.20

Maskuerade 是一种特殊的 SNAT,数据包从那个网卡发出,就使用该网卡的 IP 做 SNAT,具体使用哪个 IP 由内核决定

iptables -t nat -A POSTROUTING -s 192.168.10.0/24 -j MASQUERADE

iptables 配合 ipset 实现批量 IP 控制

ipset 是 Linux 内核中的一个工具,根据创建 ipset 时指定的类型,它可以存储多个 IP 地址、IP 网络(network)、TCP/UDP 端口、MAC 地址等,并且支持动态更新其中的内容。比如配合 iptables 使用时,可以将多个目标 IP 地址或者网络存储于其中并可以动态更新 iptables 规则(更新 ipset 后无需重启 iptables 即可生效)

  1. 创建 ipset 并指定 ipset 类型为 hash:ip,更多支持的类型可以通过 ipset -h 查看

    ipset create allowed_ips hash:ip

  2. 添加目标 IP 到 ipset,可以多次添加

    ipset add allowed_ips 2.9.1.171
  3. iptables 中使用 ipset

    # ipset 作为源 IP 允许访问
    iptables -A INPUT -m set --match-set allowed_ips src -j ACCEPT

    # 源 IP 不在 ipset 中则不允许访问
    iptables -A INPUT -m set ! --match-set allowed_ips src -j DROP
  4. 默认情况下,ipset 属于临时配置,没有持久化,系统重启后内容会丢失。要配置 ipset 持久化,可以参考以下步骤

    # 保存 ipset 配置
    ipset save > /etc/ipset.conf

    # 恢复 ipset 配置
    ipset restore < /etc/ipset.conf

docker compose 场景下使用 iptables 进行白名单限制

假设有以下 docker-compose.yml 文件

docker-compose.yml
version: "3"

services:
nextcloud_aio_mastercontainer:
image: nextcloud/all-in-one:latest
container_name: nextcloud-aio-mastercontainer
ports:
- "8000:80"
- "8080:8080"
- "8443:8443"
volumes:
- aio_mastercontainer:/mnt/docker-aio-config
- /var/run/docker.sock:/var/run/docker.sock:ro

volumes:
aio_mastercontainer:
driver: local
driver_opts:
type: none
o: bind
device: ./data/

docker compose up 正常启动后, Docker 会根据 docker-compose.yml 中的定义配置 iptables 防火墙规则,以下为上面 docker-compose.yml 定义的数据端口在 iptables 规则中的数据流向分析

  1. 数据报文首先经过 iptablesPREROUTING 链,在 PREROUTING 链中会进行 DNAT,位于 nat 表中,查看其具体规则
    # iptables -t nat -L -v -n
    Chain PREROUTING (policy ACCEPT 4376 packets, 255K bytes)
    pkts bytes target prot opt in out source destination
    80051 4690K DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL

    Chain INPUT (policy ACCEPT 3891 packets, 233K bytes)
    pkts bytes target prot opt in out source destination

    Chain OUTPUT (policy ACCEPT 12629 packets, 1082K bytes)
    pkts bytes target prot opt in out source destination
    0 0 DOCKER all -- * * 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL

    Chain POSTROUTING (policy ACCEPT 12637 packets, 1083K bytes)
    pkts bytes target prot opt in out source destination
    3 200 MASQUERADE all -- * !br-43e659324492 172.21.0.0/16 0.0.0.0/0
    0 0 MASQUERADE all -- * !docker0 172.17.0.0/16 0.0.0.0/0
    0 0 MASQUERADE tcp -- * * 172.21.0.2 172.21.0.2 tcp dpt:8443
    0 0 MASQUERADE tcp -- * * 172.21.0.2 172.21.0.2 tcp dpt:8080
    0 0 MASQUERADE tcp -- * * 172.21.0.2 172.21.0.2 tcp dpt:80

    Chain DOCKER (2 references)
    pkts bytes target prot opt in out source destination
    0 0 RETURN all -- br-43e659324492 * 0.0.0.0/0 0.0.0.0/0
    0 0 RETURN all -- docker0 * 0.0.0.0/0 0.0.0.0/0
    3 128 DNAT tcp -- !br-43e659324492 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8443 to:172.21.0.2:8443
    3 128 DNAT tcp -- !br-43e659324492 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.21.0.2:8080
    2 100 DNAT tcp -- !br-43e659324492 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8000 to:172.21.0.2:80
    1. DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL : 所有的报文会被跳转到 DOCKER 链进行处理
    2. DNAT tcp -- !br-43e659324492 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8443 to:172.21.0.2:8443 : 只要不是从 br-43e659324492 网卡(本示例中的 docker-compose.yml 使用的网络)进入的数据包,不论目标网卡/源 IP/目标 IP 为何,只有其目标端口为 8443,则对其进行 DNAT,将其目标 IP 和端口修改为 172.21.0.2:8443(本示例中的容器)。其他 2 条 DNAT 同理。
  2. iptablesPREROUTING 链处理完成后,数据流入 iptablesFORWARD。规则定义位于 filter 表中。查看其具体规则
    # iptables -L -v -n --line-numbers
    Chain INPUT (policy DROP 0 packets, 0 bytes)
    num pkts bytes target prot opt in out source destination
    1 605K 585M ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
    2 0 0 ACCEPT all -- lo * 0.0.0.0/0 0.0.0.0/0
    ...
    9 461 33197 ACCEPT icmp -- * * 0.0.0.0/0 0.0.0.0/0 icmptype 8
    10 178 4984 ACCEPT icmp -- * * 0.0.0.0/0 0.0.0.0/0 icmptype 0
    11 10055 481K REJECT all -- * * 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited

    Chain FORWARD (policy DROP 0 packets, 0 bytes)
    num pkts bytes target prot opt in out source destination
    1 259 186K DOCKER-USER all -- * * 0.0.0.0/0 0.0.0.0/0
    2 259 186K DOCKER-ISOLATION-STAGE-1 all -- * * 0.0.0.0/0 0.0.0.0/0
    3 128 179K ACCEPT all -- * br-43e659324492 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
    4 8 356 DOCKER all -- * br-43e659324492 0.0.0.0/0 0.0.0.0/0
    5 123 7065 ACCEPT all -- br-43e659324492 !br-43e659324492 0.0.0.0/0 0.0.0.0/0
    6 0 0 ACCEPT all -- br-43e659324492 br-43e659324492 0.0.0.0/0 0.0.0.0/0
    7 0 0 ACCEPT all -- * docker0 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
    8 0 0 DOCKER all -- * docker0 0.0.0.0/0 0.0.0.0/0
    9 0 0 ACCEPT all -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0
    10 0 0 ACCEPT all -- docker0 docker0 0.0.0.0/0 0.0.0.0/0
    11 0 0 REJECT all -- * * 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited

    Chain OUTPUT (policy ACCEPT 42129 packets, 7533K bytes)
    num pkts bytes target prot opt in out source destination

    Chain DOCKER (2 references)
    num pkts bytes target prot opt in out source destination
    1 3 128 ACCEPT tcp -- !br-43e659324492 br-43e659324492 0.0.0.0/0 172.21.0.2 tcp dpt:8443
    2 3 128 ACCEPT tcp -- !br-43e659324492 br-43e659324492 0.0.0.0/0 172.21.0.2 tcp dpt:8080
    3 2 100 ACCEPT tcp -- !br-43e659324492 br-43e659324492 0.0.0.0/0 172.21.0.2 tcp dpt:80

    Chain DOCKER-ISOLATION-STAGE-1 (1 references)
    num pkts bytes target prot opt in out source destination
    1 123 7065 DOCKER-ISOLATION-STAGE-2 all -- br-43e659324492 !br-43e659324492 0.0.0.0/0 0.0.0.0/0
    2 0 0 DOCKER-ISOLATION-STAGE-2 all -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0
    3 259 186K RETURN all -- * * 0.0.0.0/0 0.0.0.0/0

    Chain DOCKER-ISOLATION-STAGE-2 (2 references)
    num pkts bytes target prot opt in out source destination
    1 0 0 DROP all -- * br-43e659324492 0.0.0.0/0 0.0.0.0/0
    2 0 0 DROP all -- * docker0 0.0.0.0/0 0.0.0.0/0
    3 123 7065 RETURN all -- * * 0.0.0.0/0 0.0.0.0/0

    Chain DOCKER-USER (1 references)
    num pkts bytes target prot opt in out source destination
    1 259 186K RETURN all -- * * 0.0.0.0/0 0.0.0.0/0
    1. 数据流进入 iptablesFORWARD 链。
    2. DOCKER-USER all -- * * 0.0.0.0/0 0.0.0.0/0 : 首先会被全部跳转到 DOCKER-USER 链进行处理。
    3. RETURN all -- * * 0.0.0.0/0 0.0.0.0/0 : 默认情况下,DOCKER-USER 链未作任何处理,数据流跳转回 FORWARD 链中的原位置,继续进入下一个链 DOCKER-ISOLATION-STAGE-1
    4. ACCEPT all -- * br-43e659324492 0.0.0.0/0 0.0.0.0/0 : 数据流到了此处,表示目标网络为本示例中的容器的网络,对数据全部放行接收。数据进入容器。

根据以上对数据流向的分析,如果要对进入容器的数据进行源 IP 限制,建议将其放置在 DOCKER-USER 链中。具体实现参考如下:

  1. 配置 ipset,用于对多个源 IP 设置白名单

    ipset create allowed_ips hash:ip

    ipset add allowed_ips 2.9.1.171
  2. 配置 iptables,如果源 IP 不在白名单中,则禁止访问特定的端口

    iptables -t filter -I DOCKER-USER 1 -m set ! --match-set allowed_ips src -m multiport -p tcp --dports 8000,8080,8443 -j DROP -m comment --comment "for Nextcloud"

常见问题

ssh 无法连接

iptables 配置文件内容如下,关闭防火墙后,ssh 可以从客户端 192.168.1.2 进行连接。重启防火墙后,则无法连接。说明问题是因为防火墙导致。

/etc/sysconfig/iptables
*filter
:INPUT DROP [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [4:368]
# 允许本机访问外网
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
# 回环网卡配置
-A INPUT -i lo -j ACCEPT

# sshd
-A INPUT -m comment --comment "ssh" -p tcp -m state --state NEW -m tcp -s 192.168.1.2 --dport 22 -j ACCEPT

-A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 0 -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT

针对 sshd 的防火墙规则添加日志,配置文件修改如下

/etc/sysconfig/iptables
*filter
:INPUT DROP [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [4:368]
# 允许本机访问外网
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
# 回环网卡配置
-A INPUT -i lo -j ACCEPT

# sshd
-A INPUT -p tcp -s 192.168.1.2 --dport 22 -j LOG --log-prefix 'iptables-sshd'
-A INPUT -m comment --comment "ssh" -p tcp -m state --state NEW -m tcp -s 192.168.1.2 --dport 22 -j ACCEPT

-A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 0 -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT

主要添加日志规则: -A INPUT -p tcp -m state --state NEW -m tcp -s 192.168.1.2 --dport 22 -j LOG --log-prefix 'iptables-sshd' 日志规则配置参考

ssh 连接失败后查看日志

Feb 17 13:32:02 ip-172-31-2-4 kernel: iptables-sshdIN=eth0 OUT= MAC=02:d4:c1:a7:73:bb:02:69:00:94:63:ed:08:00 SRC=192.168.1.2 DST=172.31.2.4 LEN=60 TOS=0x00 PREC=0x00 TTL=42 ID=61621 DF PROTO=TCP SPT=53576 DPT=22 WINDOW=26883 RES=0x00 SYN URGP=0
Feb 17 13:32:04 ip-172-31-2-4 kernel: iptables-sshdIN=eth0 OUT= MAC=02:d4:c1:a7:73:bb:02:69:00:94:63:ed:08:00 SRC=192.168.1.2 DST=172.31.2.4 LEN=60 TOS=0x00 PREC=0x00 TTL=42 ID=61622 DF PROTO=TCP SPT=53576 DPT=22 WINDOW=26883 RES=0x00 SYN URGP=0
Feb 17 13:34:13 ip-172-31-2-4 kernel: iptables-sshdIN=eth0 OUT= MAC=02:d4:c1:a7:73:bb:02:69:00:94:63:ed:08:00 SRC=192.168.1.2 DST=172.31.2.4 LEN=60 TOS=0x00 PREC=0x00 TTL=42 ID=56660 DF PROTO=TCP SPT=53746 DPT=22 WINDOW=26883 RES=0x00 SYN URGP=0

根据日志可知,规则 INPUT -p tcp -s 192.168.1.2 --dport 22 可以匹配到 ssh 的流量,说明 filter 表的 INPUT 链配置无误,根据 -j ACCEPTfilter 表的 INPUT 链会 ACCEPT 此连接请求。连接无法建立,应该是因为其他防火墙规则导致。

检查 filter 表之外的其他表的 INPUT 链,看是否存在拒绝的规则,发现 NAT 表中,INPUT 链的规则默认为 DROP

$ iptables -t nat -L -v -n --line-numbers
Chain PREROUTING (policy ACCEPT 31 packets, 1742 bytes)
num pkts bytes target prot opt in out source destination

Chain INPUT (policy DROP 6 packets, 360 bytes)
num pkts bytes target prot opt in out source destination

Chain OUTPUT (policy ACCEPT 5 packets, 367 bytes)
num pkts bytes target prot opt in out source destination

Chain POSTROUTING (policy ACCEPT 5 packets, 367 bytes)
num pkts bytes target prot opt in out source destination

将其默认规则改为 ACCEPT

iptables -t nat -P INPUT ACCEPT

重新使用 ssh 登陆,连接正常。