Salt 之 ipset 模块实战:用执行模块与状态模块管理 iptables IP 集合
【免费下载链接】saltSoftware to automate the management and configuration of infrastructure and applications at scale.项目地址: https://gitcode.com/gh_mirrors/sa/salt
本文基于当前开源仓库中的 ipset 模块文档(doc/ref/modules/all/salt.modules.ipset.rst)展开,系统讲解 Salt 如何通过执行模块(salt.modules.ipset)与状态模块(salt.states.ipset)管理 Linux 内核的 IP 集合(ipset),用于配合 iptables 防火墙实现高效的 IP/端口/网段批量匹配。读完本文,你将掌握 ipset 集合的创建、增删改查、清空等全部命令的调用方式,理解底层参数映射与校验逻辑,并能在 SLS 状态文件中实现集合与成员的幂等管理。
模块定位与加载条件
ipset 模块用于操作 Linux 内核提供的 ipset 工具——一种面向 iptables 的高性能集合数据结构,可将大量 IP 地址、网段、端口、MAC 地址等组织为命名集合,供防火墙规则引用,避免逐条编写 iptables 规则。Salt 将该能力封装为统一接口,便于通过salt '*' ipset.xxx批量下发到所有 minion。
从源码 salt/modules/ipset.py 可见,模块的__virtual__()会检查系统中是否存在ipset二进制文件:
def __virtual__(): if salt.utils.path.which("ipset"): return True return ( False, "The ipset execution modules cannot be loaded: ipset binary not in path.", )也就是说,只有在 minion 上安装了ipset命令(如yum install ipset或apt install ipset)时,模块才会被加载,否则返回加载失败原因。状态模块 salt/states/ipset.py 则依赖执行模块是否可用("ipset.version" in __salt__)来决定是否加载。
集合类型与地址族映射
模块内部维护了两张核心常量表,这是理解所有命令参数的基础。
支持的集合类型_IPSET_SET_TYPES
源码 salt/modules/ipset.py 定义了模块支持的全部 15 种集合类型:
| 集合类型 | 说明 |
|---|---|
bitmap:ip | IP 位图(必须指定 range) |
bitmap:ip,mac | IP+MAC 位图 |
bitmap:port | 端口位图 |
hash:ip | IP 哈希 |
hash:mac | MAC 哈希 |
hash:ip,port | IP+端口哈希 |
hash:ip,port,ip | IP+端口+IP 哈希 |
hash:ip,port,net | IP+端口+网段哈希 |
hash:net | 网段哈希 |
hash:net,net | 网段+网段哈希 |
hash:net,iface | 网段+网卡接口哈希 |
hash:net,port | 网段+端口哈希 |
hash:net,port,net | 网段+端口+网段哈希 |
hash:ip,mark | IP+防火墙标记(mark)哈希 |
list:set | 集合的列表(可聚合其他集合) |
地址族映射_IPSET_FAMILIES
源码 salt/modules/ipset.py 将 family 参数映射为 ipset 命令内部的 inet 族:
_IPSET_FAMILIES = { "ipv4": "inet", "ip4": "inet", "ipv6": "inet6", "ip6": "inet6", }即family参数既支持完整的ipv4/ipv6,也兼容ip4/ip6缩写。默认值为ipv4。
执行模块函数全解析
以下函数均位于 salt/modules/ipset.py,命令通过__salt__["cmd.run"]在 minion 本地执行,python_shell=False保证不经过 shell 解释。
version——查看 ipset 版本
执行ipset --version并解析出版本号:
salt '*' ipset.version实现见 salt/modules/ipset.py,先运行ipset --version,对输出按空白切分后取第二个字段作为版本返回。
new_set——创建集合
new_set是使用频率最高的函数之一,负责创建自定义集合。其签名与典型调用如下(自 2014.7.0 版本加入):
def new_set(name=None, set_type=None, family="ipv4", comment=False, **kwargs):CLI 示例(源码 salt/modules/ipset.py):
# 创建一个 list:set 类型的集合 salt '*' ipset.new_set custom_set list:set # 带注释创建 salt '*' ipset.new_set custom_set list:set comment=True # IPv6 salt '*' ipset.new_set custom_set list:set family=ipv6创建过程会依次校验:集合名必须指定、集合类型必须指定、类型必须在_IPSET_SET_TYPES中。随后通过_CREATE_OPTIONS_REQUIRED检查该类型必须携带的参数(如bitmap:ip必须提供range),再按_CREATE_OPTIONS中的选项表拼接ipset create <name> <type> [options]命令。
各类型创建选项表(_CREATE_OPTIONS)
源码 salt/modules/ipset.py 定义了每种类型可用的创建选项:
- bitmap 系列(
bitmap:ip、bitmap:ip,mac、bitmap:port):range(必填)、timeout、counters、comment、skbinfo - hash 系列(
hash:ip、hash:net、hash:net,net、hash:net,port、hash:net,port,net、hash:ip,port、hash:ip,port,ip、hash:ip,port,net、hash:net,iface):family、hashsize、maxelem、netmask、timeout、counters、comment、skbinfo hash:mac:hashsize、maxelem、timeout、counters、comment、skbinfohash:ip,mark:额外支持markmasklist:set:size、timeout、counters、comment
其中comment、counters、skbinfo属于无值选项(_CREATE_OPTIONS_WITHOUT_VALUE,见 salt/modules/ipset.py),拼接命令时不带值,直接追加选项名。
必填选项表(_CREATE_OPTIONS_REQUIRED)
源码 salt/modules/ipset.py 中,仅 bitmap 系列要求range必填,其余 hash/list 系列无必填项:
_CREATE_OPTIONS_REQUIRED = { "bitmap:ip": ["range"], "bitmap:ip,mac": ["range"], "bitmap:port": ["range"], "hash:ip": [], # ... 其余 hash/list 类型均为空列表 }此外,仅当集合类型支持family选项时才追加family <inet|inet6>参数;comment=True时追加comment标志。命令成功(无输出)时返回True。
单元测试 tests/pytests/unit/modules/test_ipset.py 验证了缺失名称、缺失类型、非法类型、bitmap 缺range时的错误返回,以及成功路径返回True的行为。
delete_set——删除集合
执行ipset destroy <name>删除整个集合(自 2014.7.0 加入):
salt '*' ipset.delete_set custom_set salt '*' ipset.delete_set custom_set family=ipv6实现见 salt/modules/ipset.py,未指定名称时返回"Error: Set needs to be specified"。
rename_set——重命名集合
salt '*' ipset.rename_set custom_set new_set=new_set_name salt '*' ipset.rename_set custom_set new_set=new_set_name family=ipv6实现见 salt/modules/ipset.py。重命名前会先通过_find_set_type确认原集合存在、新名称未被占用,两个前置校验失败分别返回"Error: Set does not exist"和"Error: New Set already exists",再执行ipset rename <old> <new>。
list_sets——列出所有集合
salt '*' ipset.list_sets实现见 salt/modules/ipset.py。执行ipset list -t,将输出按空行切分为多个集合对象,每一行按key: value解析成字典,返回由多个字典组成的列表,每个字典含Name、Type、Header等字段。
check_set——检查集合是否存在
salt '*' ipset.check_set name实现见 salt/modules/ipset.py。内部调用_find_set_info(执行ipset list -t <name>),集合存在返回True,不存在返回False。
add——向集合追加条目
# 追加单个 IP salt '*' ipset.add name 192.168.1.26 # 追加 IP+MAC 复合条目 salt '*' ipset.add name 192.168.0.3,AA:BB:CC:DD:EE:FF # 带注释(entry 内联写法) salt '*' ipset.add name '192.168.0.1 comment "Hello"'实现见 salt/modules/ipset.py,是校验最丰富的函数之一:
- 名称与条目必填,集合必须存在(
_find_set_info返回集合元信息,包括Type与Header); - 命令使用
ipset add -exist <name> <entry...>,-exist保证条目已存在时不会报错,同时可用于更新条目的注释; - 选项与集合创建能力绑定校验:
- 传入
timeout时,集合 Header 必须包含timeout(否则返回"Error: Set {name} not created with timeout support"); - 传入
packets/bytes时,集合必须创建于counters支持; - 传入
comment时,集合必须创建于comment支持; - 传入
skbmark/skbprio/skbqueue时,集合必须创建于skbinfo支持;
- 传入
- 通过
_ADD_OPTIONS选项表(见 salt/modules/ipset.py)拼接条目附加选项,如hash:net系列额外支持nomatch; - 先读取集合现有成员,若条目已存在返回
"Warn: Entry {entry} already exists in set {name}"; - 命令成功返回
"Success",失败返回"Error: {输出}"。
单元测试 tests/pytests/unit/modules/test_ipset.py 覆盖了缺失参数、集合不存在、timeout/counters/comment 能力校验、重复条目告警以及成功路径。功能测试 tests/pytests/functional/modules/test_ipset.py 则演示了真实环境下的bitmap:ip集合创建与条目添加。
delete——删除集合条目
salt '*' ipset.delete name 192.168.0.3,AA:BB:CC:DD:EE:FF实现见 salt/modules/ipset.py。校验集合存在后执行ipset del <name> <entry>,成功返回"Success"。
check——检查条目是否在集合中(智能匹配)
check是模块中智能度最高的函数,它不只是简单执行ipset test,而是读取集合成员后用 Python 的ipaddress库做语义化匹配:
# 单 IP salt '*' ipset.check name 192.168.0.1 # IP 范围 salt '*' ipset.check name 192.168.0.2-192.168.0.19 # 子网 salt '*' ipset.check name 192.168.0.0/25 # 带注释的条目 salt '*' ipset.check name '192.168.0.1 comment "Hello"'参数说明(源码 salt/modules/ipset.py):
name:集合名;entry:单个 IP、IP 范围或子网块;支持传列表;family:ipv4 或 ipv6。
匹配逻辑依赖辅助函数链:_parse_members→_parse_member→_member_contains→_compare_member_parts(见 salt/modules/ipset.py)。核心能力包括:
- 按集合类型(如
hash:net)拆分子类型,ip/net字段尝试解析为ipaddress.IPv4Network/IPv6Network或IPv4Address/IPv6Address,port转为整数; - IP 范围(
a-b)用summarize_address_range展开为网段列表; - 子网包含关系判断:如集合成员为
192.168.0.4/31,检查192.168.0.4/30时能正确判定包含/被包含关系(见 salt/modules/ipset.py)。
单元测试 tests/pytests/unit/modules/test_ipset.py 对hash:ip与hash:net两种类型分别验证了单 IP、/31网段、范围条目的匹配结果。
test——用内核测试条目是否在集合中
与check的语义化解析不同,test直接调用底层ipset test <name> <entry>命令,依据退出码判断:
salt '*' ipset.test name 192.168.0.2 salt '*' ipset.test name fd81:fc56:9ac7::/48 # IPv6实现见 salt/modules/ipset.py:使用cmd.run_all执行,retcode > 0返回False,否则返回True。单元测试 tests/pytests/unit/modules/test_ipset.py 验证了该退出码判定逻辑。
flush——清空集合条目
不指定集合时清空所有集合,指定时只清空该集合:
salt '*' ipset.flush salt '*' ipset.flush set salt '*' ipset.flush # IPv6 同理 salt '*' ipset.flush set实现见 salt/modules/ipset.py:执行ipset flush [name],命令无输出(成功)时返回True。
状态模块:以声明式 SLS 管理 ipset
salt.states.ipset(源码 salt/states/ipset.py)将上述执行模块封装为幂等的声明式状态,自 2014.7.0 加入,共五个状态函数。所有状态均遵循 Salt 标准返回结构(name、changes、result、comment),并支持test=True试运行模式(此时返回result: None与“would be...”注释)。
set_present / set_absent——管理集合本身
setname: ipset.set_present: - set_type: bitmap:ip - range: 192.168.0.0/16 - comment: True setname: ipset.set_absent: - set_type: bitmap:ip - range: 192.168.0.0/16 - comment: Trueset_present(salt/states/ipset.py):先check_set判断,已存在则直接成功;test 模式提示“would be added”;否则调用ipset.new_set,成功后记录 changes。set_absent(salt/states/ipset.py):集合不存在视为成功;存在时先ipset.flush清空,再ipset.delete_set删除。
present / absent——管理集合条目
setname_entries: ipset.present: - set_name: setname - entry: 192.168.0.3 - comment: Hello - require: - ipset: baz setname_entries: ipset.present: - set_name: setname - entry: - 192.168.0.3 - 192.168.1.3 - comment: Hello - require: - ipset: baz setname_entries: ipset.absent: - set_name: setname - entry: - 192.168.0.3 - 192.168.1.3 - comment: Hello - require: - ipset: bazpresent(salt/states/ipset.py):name只是状态内部的用户自定义标识,并非真实条目;entry支持单个值或列表。状态会自动把timeout、comment等 kwargs 拼入条目字符串(如timeout 300 comment Hello),先ipset.check判断条目是否已存在,不存在才调用ipset.add。absent(salt/states/ipset.py):逻辑相反,条目已不在集合中视为成功,存在则调用ipset.delete移除。
flush——清空指定集合
setname: ipset.flush:实现见 salt/states/ipset.py:集合不存在直接失败;test 模式提示“would be flushed”;否则调用执行模块的ipset.flush。
功能测试 tests/pytests/functional/states/test_ipset.py 验证了states.ipset.present在真实 ipset 环境下的添加行为与注释输出格式。
典型实战:配合 iptables 的完整链路
结合执行模块与状态模块,一个典型场景——在 minion 上维护“黑名单 IP 集合”并让 iptables 引用它:
# 1. 创建 hash:net 集合(支持网段与超时) salt '*' ipset.new_set blacklist hash:net family=ipv4 timeout=86400 comment=True # 2. 添加条目 salt '*' ipset.add blacklist '203.0.113.5' salt '*' ipset.add blacklist '198.51.100.0/24 comment "spam net"' # 3. 校验 salt '*' ipset.list_sets salt '*' ipset.check blacklist '198.51.100.0/24' salt '*' ipset.test blacklist 203.0.113.5 # 4. 清空/删除 salt '*' ipset.flush blacklist salt '*' ipset.delete_set blacklist等价的状态声明:
blacklist: ipset.set_present: - set_type: hash:net - family: ipv4 - timeout: 86400 - comment: True blacklist_entries: ipset.present: - set_name: blacklist - entry: - 203.0.113.5 - 198.51.100.0/24 - comment: 'spam net' - require: - ipset: blacklist随后在 iptables 中引用该集合(可配合 Salt 的 iptables 状态模块):
iptables -A INPUT -m set --match-set blacklist src -j DROP注意事项与适用前提
- 依赖 ipset 二进制:模块通过
which("ipset")检测,未安装时执行模块与状态模块均不加载,详见 salt/modules/ipset.py。 - 能力校验是双向的:向集合
add带 timeout/counters/comment/skbinfo 的条目时,集合本身必须在创建时启用了对应能力,否则返回明确错误。 check与test语义不同:test依赖内核精确匹配;check由 Python 侧解析 IP 对象,支持范围/子网包含关系判断,适合状态模块做幂等判断,但也依赖集合类型解析正确。- 条目内联注释:
check与add均支持在 entry 中携带comment "xxx"片段(见 salt/modules/ipset.py),状态模块会自动拼装timeout/comment到条目字符串中。 - IPv6 支持:所有函数均接受
family=ipv6(或ip6),最终映射为inet6族。 - 执行环境为 Linux 内核 ipset:该模块面向 Linux 系统,Windows 下不可用(功能测试也通过
skip_if_binaries_missing("ipset")跳过)。
延伸阅读
- 执行模块完整实现:salt/modules/ipset.py
- 状态模块完整实现:salt/states/ipset.py
- 状态模块文档:doc/ref/states/all/salt.states.ipset.rst
- 单元测试:tests/pytests/unit/modules/test_ipset.py
- 功能测试:tests/pytests/functional/modules/test_ipset.py、tests/pytests/functional/states/test_ipset.py
【免费下载链接】saltSoftware to automate the management and configuration of infrastructure and applications at scale.项目地址: https://gitcode.com/gh_mirrors/sa/salt
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考