Python多线程操作iptc库修改iptables规则偶发Segmentation fault求助
错误触发原因
- 你使用的
python-iptc库底层封装了Linux原生的libiptc库,该库本身不支持多线程并发操作,没有做并发访问的内存安全防护。 - 你的代码中全局共享同一个
Table和Chain实例,当多个子线程同时执行插入/删除规则操作、触发底层commit动作时,会出现多线程同时读写同一块内核态/用户态缓存内存的情况,直接触发段错误(SIGSEGV),和你报错日志中commit行触发崩溃的现象完全吻合。 - 另外全局初始化的
Table实例存在缓存过期风险:如果系统其他进程修改了iptables规则,你缓存的Table对象状态和实际内核状态不一致,commit时也会触发内存访问异常。
解决方案
按优先级从高到低推荐:
- 替换iptc库,直接调用系统iptables命令
虽然调用子进程执行命令有轻微性能损耗,但完全规避了底层库的线程安全问题,稳定性最高,适合你的业务场景(规则添加频率不会特别高)。操作示例:import subprocess def add_rules(sp): # 添加规则 subprocess.run( ["iptables", "-I", "INPUT", "-s", sp, "-j", "ACCEPT"], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL ) print(f'rule {sp} insert! {time.time()}') time.sleep(30) # 删除规则 subprocess.run( ["iptables", "-D", "INPUT", "-s", sp, "-j", "ACCEPT"], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL ) print(f'time out delete {sp} this rule! {time.time()}') - 保留iptc库,加互斥锁+每次操作重新初始化实例
如果一定要用iptc库,必须保证所有iptc操作串行执行,且不共享全局实例:- 全局创建一个线程锁,所有操作iptc的代码块都加锁
- 不要用全局的Table、Chain对象,每次执行规则操作前重新初始化实例
代码示例:
import threading import time from scapy.all import sniff, IP, UDP import iptc # 全局互斥锁,保证同一时间只有一个线程操作iptc iptc_lock = threading.Lock() def Callback(packet): if packet[IP].proto == 17 and packet[UDP].dport == 62201: sp = packet[IP].src thread_add = threading.Thread(target=add_rules, args=(sp, )) thread_add.start() def add_rules(sp): # 操作前加锁 with iptc_lock: # 每次操作重新初始化table和chain,不共享全局实例 table = iptc.Table(iptc.Table.FILTER) chain = iptc.Chain(table, "INPUT") rule = iptc.Rule() rule.src = sp target = iptc.Target(rule, "ACCEPT") rule.target = target chain.insert_rule(rule) # 主动提交并关闭table,释放缓存 table.commit() table.close() print(f'rule {sp} insert! {time.time()}') time.sleep(30) # 删除操作同样加锁,重新初始化实例 with iptc_lock: table = iptc.Table(iptc.Table.FILTER) chain = iptc.Chain(table, "INPUT") rule = iptc.Rule() rule.src = sp target = iptc.Target(rule, "ACCEPT") rule.target = target chain.delete_rule(rule) table.commit() table.close() print(f'time out delete {sp} this rule! {time.time()}') if __name__ == "__main__": while 1: print('start sniffing') msg = sniff(filter='dst port 62201', prn=Callback) time.sleep(0.03) - 额外优化建议:如果你的业务场景允许,可以用
ipset替代单条iptables规则添加,将需要放行的IP加入ipset,只用一条iptables规则匹配ipset即可,规则操作效率更高,也能进一步降低冲突概率。
内容的提问来源于stack exchange,提问作者BlubberLeo
相关产品推荐
相关产品推荐

