版本比较

标识

  • 该行被添加。
  • 该行被删除。
  • 格式已经改变。

iptables 和容器相关

Docker、K8S 和 CNI(flannel、calico 等等) 都使用了 iptables:

  • docker 的 -p
  • docker 桥接网络下容器访问外部网络
  • K8S 的 Service
  • pod 访问其他的 node IP 或者外部网络
  • pod 的 hostPort
  • NetworkPolicy

iptables 知识点非常广,全部讲解到的话是可以单独出一本书的,所以只介绍基础知识。

数据中心中有硬件防火墙,Linux 也有自己的防火墙,它就是 netfilter 安全框架,字面意思 “网络过滤” ,netfilter 处于内核空间,iptables 是命令行工具,我们用这个工具来增删改查 netfilter 提供的 hook 点上的规则(rule)。

如何学习 iptables

对于 iptables 基础教程,推荐先去看完 朱双印 iptables 详解系列,看完后接着看下面的。

很多介绍 iptables 的文章都画了类似的图,虽然风格和位置不一样,但是核心都是是五链(netfilter 提供的 hook 点)五表(存储链的规则,对于匹配的包执行什么行为):

  • raw、mangle、nat、filter、security
  • PREROUTING、FORWARD、INPUT、OUTPUT、POSTROUTING

五链总是有人记不住,这东西不能死记硬背,最简单的一个记忆方法根据网络情况记忆:

  • 数据包进协议栈,和协议栈发包出去,就是 INPUT、OUTPUT
  • Linux 有路由表,想到路由表,你就能想到路由前和路由后,也就是 PRE_ROUTING 、POST_ROUTING
  • Linux 内核参数可以开启转发,开启后对于目标 IP 不是自己的可以转发出去。也就是 FORWARD

五表:

  • filter: 过滤报文,例如 DROP、REJECT、ACCEPT,iptables 默认缺省的表,例如 iptables 操作规则时候默认就是 iptables -t filter
  • nat: 地址转换, DNAT(POSTROUTING 做目标地址转换)和 SNAT (在 PREROUTING 做源地址转换)以及动态获取网卡 IP 做 SNAT 的 MASQUERADE
  • raw:优先级最高的表,iptables 是有状态的,一般用 -j NOTRACK 关闭匹配包的链路追踪(connection tracking)提高性能和 CT (conntrack table)行为(-j CT --helper ftp)
  • mangle:拆解报文,做出修改,并重新封装,例如 MTU、TTL、TOS,还有修改 MARK 来配合做策略路由,以及 tproxy 透明代理。
  • security:后面新加的表,SELinux 相关安全使用

iptables 常见概念和用法

iptables 基础知识点

  • Linux 的 firewalld 和 ufw 之类的关闭了,并不是代表 iptables 规则就不生效了,firewalld 和 ufw 只是更上层抽象后的易用和更丰富的一个管理工具,旨在减少对于底层的了解。这类防火墙默认策略都是拒绝的,所以很多人喜欢到手就关闭它们,有个要注意的点 systemctl stop firewalld 关闭 firewalld 会清空 iptables 规则,在运行 Docker 过程中停止 firewallld 会造成后续 docker run -p 报错 NO Chain 之类的。
  • iptables 对规则增删改查会使用文件锁 /run/xtables.lock,可以使用 -w/--wait 选项避免竞争

iptables 常见命令

很多教程教查看 iptables 规则是:

代码块
languagebash
iptables -nvL
iptables -nvL INPUT

这个非常不适合 iptables 新手阅读使用,而是应该 iptables -S:

代码块
languagebash
$ iptables -S
-P INPUT ACCEPT
-P FORWARD DROP #<- 有些应用启动后,会把默认策略设置为 DROP,导致手动做转发的时候匹配到最后被DROP掉
-P OUTPUT ACCEPT
-N BASE-RULE
-A INPUT -p icmp -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -j BASE-RULE
-A FORWARD -j ACCEPT
-A BASE-RULE -i eth0 -m set ! --match-set whiteiplist src -m set --match-set whiteportlist dst -j DROP
-A BASE-RULE -j RETURN

该命令会输出 iptables 命令行风格的规则。假如外部无法访问上面机器的 80 端口,只能 ping 通,你会隐约感觉是 match-set 这条规则导致的,而假设该规则你又看不懂具体是干啥的,可以两个思路:

注意
外面一台机器 192.168.1.2 频繁触发访问 80 端口

1、计数器

代码块
languagebash
# 清空计数器
iptables --wait --zero INPUT
iptables --wait --zero BASE-RULE
# 观察计数器增加,看匹配到哪条链下的哪个规则
ipatbles --wait -nvL INPUT
ipatbles --wait -nvL BASE-RULE

2、二分法,增加放行,例如

代码块
languagebash
# 明细的来源 IP 和目标端口放行,特别是线上的时候
# insert 是最前面插入
iptables --wait --insert INPUT --source 192.168.1.2/32 -p tcp --dport 80 -j ACCEPT
# 外面能通了后删除该规则,也能 iptables --wait --delete INPUT 1 删除该规则
iptables --wait --delete INPUT --source 192.168.1.2/32 -p tcp --dport 80 -j ACCEPT

iptables --wait --insert INPUT 2 --source 192.168.1.2/32 -p tcp --dport 80 -j ACCEPT
iptables --wait --delete INPUT 2

iptables --wait --insert BASE-RULE --source 192.168.1.2/32 -p tcp --dport 80 -j ACCEPT
iptables --wait --delete BASE-RULE 1

最后以此内推,定位到是 BASE-RULE 链里的那个看不懂的规则,生产环境特别是部署了 K8S 后,iptables -nvL 的计数器会因为流量大而不容易观察,更多是后面说的二分法定位大概规则范围。

iptables 的规则就是 匹配条件 行为,下面列举一些规则:

代码块
languagebash
# 使用 --insert 短选项 -I 和省略 --wait 举例了,实践的话最好找个能 tty 登录的机器以免失联,每个规则记得自行删除
# 放行 icmp 协议,让外面能ping 通本机
iptables -I INPUT --protocol icmp -j ACCEPT

# 从网卡 eth0 进来,来源 IP 是 192.168.1.0/24 的直接放行
iptables -I INPUT --in-interface eth0 --source 192.168.1.0/24 -j ACCEPT

# DNS 劫持,常见用于路由器上
# 例如路由器上运行了 5300 DNS server,这个 dns server 上游用 dns over http
iptables -t nat -I PREROUTING -p udp -m udp --dport 53 -j REDIRECT --to-ports 5300
iptables -t nat -I PREROUTING -p tcp -m tcp --dport 53 -j REDIRECT --to-ports 5300

# 劫持所有 1.1.1.1:53 的 DNS 请求并重定向到 223.5.5.5:53
iptables -t nat -A PREROUTING -d 1.1.1.1 -p udp --dport 53 -j DNAT --to-destination 223.5.5.5:53
iptables -t nat -A PREROUTING -d 1.1.1.1 -p tcp --dport 53 -j DNAT --to-destination 223.5.5.5:53

# 扔掉来源端口是 8082 的出去的 tcp 包
iptables -I OUTPUT -p tcp --sport 8082 -j DROP

# 下列是组合使用的
# 已经建立链接的直接放行
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

# INPUT 链加了个BASE-RULE 链
-A INPUT -j BASE-RULE

# 表示从网卡 eth0 进来,来源 IP 不在 whiteiplist 的 ipset 里就 DROP
-A BASE-RULE -i eth0 -m set ! --match-set whiteiplist src -j DROP

# 符合了就继续往下一跳链走,会走到 RETURN,回到 INPUT 链
-A BASE-RULE -j RETURN

# 然后最后 INPUT 链默认策略是 ACCEPT,也就是 -P INPUT ACCEPT 上

最起码要先要学会看得懂 iptables 规则,熟悉了后就可以自己写规则了。

iptables 规则持久化

iptables 重启规则就会清空,所以如果不是程序添加的,人为手动添加的需要使用 iptables.service 之类的服务,例如下面的

代码块
languagebash
yum install ipset ipset-service -y
sed -ri '/^IPSET_SAVE_ON_STOP=/s#no#yes#' /etc/sysconfig/ipset-config
systemctl enable --now ipset
vi /etc/sysconfig/iptables
systemctl enable iptables

# apt 系列 os
apt update && apt install -y ipset-persistent iptables-persistent
vi /etc/iptables/rules.v4
systemctl enable netfilter-persistent.service

iptables 的两种模式

iptables 有两种模式:

iptables-legacy

  • 这是传统的 iptables 实现,使用 xtables 进行扩展。
  • 通常用于早期的 Linux 内核版本

iptables-nft

  • 基于 nftables 实现的 iptables 命令。
  • nftables 是 Netfilter 项目的一部分,旨在简化和统一 Linux 防火墙和流量控制的配置。
  • 提供了与传统 iptables 类似的命令行接口,但其底层实现使用了 nftables,带来了更好的性能和灵活性。

模式可以通过 iptables -V 查看:

代码块
languagebash
iptables v1.8.9 (nf_tables)
iptables v1.8.9 (legacy)

如果老版本的 Linux 发行版上面命令输出没有结尾的模式,都是 legacy 模式。 如果你不知道机器上两种模式规则都存在,在添加规则后会出现非预期的结果,是因为 nf_tables 优先级比 legacy 高,你如果执行 iptables -S 出现下面的:


代码块
languagebash
$ iptables -S
# Warning: iptables-legacy tables presetn. use iptables-legacy to see them

# 可以单独查看模式的规则
iptables-legacy -S
iptables-nft -S

容器里使用 iptables

特别是容器里使用 iptables 避免和宿主机 iptables 模式不一致,可以像 flannel 那样使用下列仓库的编译整进去:https://github.com/kubernetes-sigs/iptables-wrappers

这个仓库主干分支是 golang 写的,之前的 v2 版本是脚本形式的的,例如可以下面这样:

代码块
languageplain
FROM alpine:3.18
RUN set -eux; \
#    sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories; \
    apk add -u iptables  ipset  bash \
        --no-cache; \
    apk add --no-cache --virtual .curl \
        curl; \
    # 避免容器内和宿主机使用 iptables-nft iptables-legacy 模式不一致造成网络问题
    curl -L https://raw.githubusercontent.com/kubernetes-sigs/iptables-wrappers/v2/iptables-wrapper-installer.sh > /iptables-wrapper-installer.sh; \
    bash /iptables-wrapper-installer.sh --no-sanity-check; \
    apk del .curl; \
    rm -rf /var/cache/apk/* /tmp/*

另外如果容器纯粹操作 iptables 规则是不需要特权的,uid 为 0 且有对应 CAP 就可以了,例如下面的:

代码块
languageyaml
services:
  ipset:
    image: 'reg.xxx.lan:5000/xxx/ipset:v1.1'
    container_name: ipset
    network_mode: host
    hostname: ipset
    restart: always # unless-stopped
    working_dir: '/data/kube/'
    cap_drop: ["ALL"]
    cap_add: ["NET_ADMIN", "NET_RAW", "FOWNER"]
    volumes:
        # 需要挂载 `/run/` 目录,或者直接 `/run/xtables.lock` 文件
      - /run/xtables.lock:/run/xtables.lock:rw
      - '/data/kube/rule/:/data/kube/'

目录