刚刚把软路由从 Ubuntu 20.04 升级到 21.10,升级的时候告诉我会把 FirewallBackend
从 iptables
改成 nftables
。我正好想试一试,就让他改了。
结果重启之后 firewall-cmd --reload
会报错
1 2 3 4 |
Error: COMMAND_FAILED: 'python-nftables' failed: JSON blob: {"nftables": [{"metainfo": {"json_schema_version": 1}}, {"add": {"table": {"family": "inet", "name": "firewalld_policy_drop"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld_policy_drop", "name": "filter_input", "type": "filter", "hook": "input", "prio": 9, "policy": "drop"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld_policy_drop", "name": "filter_forward", "type": "filter", "hook": "forward", "prio": 9, "policy": "drop"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld_policy_drop", "name": "filter_output", "type": "filter", "hook": "output", "prio": 9, "policy": "drop"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld_policy_drop", "chain": "filter_input", "expr": [{"match": {"left": {"ct": {"key": "state"}}, "op": "in", "right": {"set": ["established", "related"]}}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld_policy_drop", "chain": "filter_forward", "expr": [{"match": {"left": {"ct": {"key": "state"}}, "op": "in", "right": {"set": ["established", "related"]}}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld_policy_drop", "chain": "filter_output", "expr": [{"match": {"left": {"ct": {"key": "state"}}, "op": "in", "right": {"set": ["established", "related"]}}}, {"accept": null}]}}}]} |
上面的JSON 内容已经给出了出错的 nftables 操作列表,firewalld 在操纵 nftables 的时候,会将多个请求合并为一个发给 nftables,导致并不能知道上面哪一条出了问题。看到bugzilla相关的讨论,可以让firewalld 拆分请求。
https://bugzilla.redhat.com/show_bug.cgi?id=1869451#c6
set IndividualCalls=yes in /etc/firewalld/firewalld.conf. This will give a better indication of what rule fails to apply to nftables.
修复了一些我自己范的简单错误后,神奇的事情发生了,如果 IndividualCalls=no
,那么问题直接消失, reload 的时候直接success 了。经过分析发现,问题还是出在 firewalld 批量操作 nftables 上。
我的推测是:如果一批操作中任何一个指令失败,这批操作就会被回滚。那么如果这批操作中有新建 Chain 之类的操作,就会导致 Chain 没有被创建,然后后面就会出各种莫名其妙的问题,我们看到的错误并非根本原因。
我这个问题的最终原因是 ipset 中 条目有overlap.删除 ipset 并导入正确的ipset之后问题解决。
调试思路
本质上,firewalld 启动的时候是不应该有任何错误才对。那么我们可以在/etc/firewalld/firewalld.conf
设置IndividualCalls=yes
,然后重启firewalld。
请注意:在这种情况下,ipset 也会一条一条的插入,如果你有上万个IP,那么这个插入速度也会非常慢。导致你可能会觉得 firewalld 根本启动不起来。那你也可以选择不设置IndividualCalls
,或者选择清空ipset以提升启动速度。
重启之后可以看看最新一次启动日志,第一个 python-nftables 相关错误是什么。如果你设置了IndividualCalls=yes
,那么此时请按从早到晚的顺序依次排查错误并解决。如果你选择保持IndividualCalls=no
,也需要查看第一个错误是什么,把 JSON blob:
后面那一段Json 保存下来。
我这里提供一个小工具,能把一批操作拆解成一个个操作,然后执行nftables。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
#!/bin/env python3 import nftables import json import argparse if __name__ == '__main__': parser = argparse.ArgumentParser("nftables-python json debugger") parser.add_argument("json_file", type=str, help='File path of json file') args = parser.parse_args() nft = nftables.Nftables() # nft.set_json_output(True) nft.set_echo_output(True) nft.set_handle_output(True) with open(args.json_file, 'r') as f: cmd_all = json.load(f) for i in range(1, len(cmd_all['nftables'])): cmd_res = { "nftables": [ cmd_all['nftables'][0], cmd_all['nftables'][i] ] } rc, output, error = nft.json_cmd(cmd_res) if rc != 0: print(cmd_res) print('rc = ', rc) print('output = ',output ) print('error = ', error) |
将刚才的 JSON 保存到 /tmp/failed-nft.json ,然后执行
1 |
sudo python nftjson.py /tmp/failed-nft.json |
就会把失败的指令和错误单独输出出来,方便调试。