270 lines
11 KiB
Python
270 lines
11 KiB
Python
# coding: utf-8
|
||
# -------------------------------------------------------------------
|
||
# yakpanel
|
||
# -------------------------------------------------------------------
|
||
# Copyright (c) 2014-2099 yakpanel(http://www.yakpanel.com) All rights reserved.
|
||
# -------------------------------------------------------------------
|
||
|
||
import subprocess
|
||
import sys
|
||
|
||
if "/www/server/panel/class" not in sys.path:
|
||
sys.path.insert(0, "/www/server/panel/class")
|
||
import public
|
||
from firewallModelV2.app.appBase import Base
|
||
|
||
|
||
class IptablesServices(Base):
|
||
def __init__(self):
|
||
super().__init__()
|
||
|
||
def set_chain_rich_ip(self, info, operation, chain):
|
||
"""
|
||
@name 添加/删除指定链的复杂ip规则
|
||
@param "data":{"参数名":""} <数据类型> 参数描述
|
||
@return dict{"status":True/False,"msg":"提示信息"}
|
||
"""
|
||
try:
|
||
if "Address" in info and info["Address"] == "":
|
||
return self._result(False, public.lang("Failed to set up rule: IP address cannot be empty"))
|
||
if "Address" in info and public.is_ipv6(info['Address']):
|
||
return self._result(False, public.lang("Failed to set up rule Unsupported IPV6 addresses"))
|
||
|
||
if "Timeout" not in info or info["Timeout"] == "":
|
||
info["Timeout"] = 0
|
||
|
||
if chain == "INPUT":
|
||
chain = 'in'
|
||
else:
|
||
chain = 'out'
|
||
|
||
if operation == "add":
|
||
exec_cmd = "ipset add {}_bt_user_{}_ipset {} timeout {}".format(chain, info["Strategy"],
|
||
info["Address"], info["Timeout"])
|
||
else:
|
||
exec_cmd = "ipset del {}_bt_user_{}_ipset {}".format(chain, info["Strategy"], info["Address"])
|
||
stdout, stderr = public.ExecShell(exec_cmd)
|
||
if stderr:
|
||
return self._result(False, public.lang(f"Failed to set up rule:{stderr}"))
|
||
|
||
return self._result(True, public.lang("Failed to set up rule"))
|
||
except Exception as e:
|
||
return self._result(False, public.lang(f"Failed to set up rule:{str(e)}"))
|
||
|
||
def rich_rules(self, info, operation, chain):
|
||
"""
|
||
@name
|
||
@author csj <2025/3/12 上午9:28>
|
||
@param "data":{"参数名":""} <数据类型> 参数描述
|
||
@return dict{"status":True/False,"msg":"提示信息"}
|
||
"""
|
||
if "Priority" in info and not "Port" in info:
|
||
return self.set_chain_rich_ip(info, operation, chain)
|
||
else:
|
||
pass
|
||
# 端口类型暂时不用新版
|
||
# return self.set_chain_rich_port(info, operation, "INPUT")
|
||
|
||
# 2024/4/29 下午4:01 获取指定链的数据
|
||
def get_chain_data(self, chain):
|
||
"""
|
||
@name 获取指定链的数据
|
||
@author wzz <2024/4/29 下午4:01>
|
||
@param "data":{"参数名":""} <数据类型> 参数描述
|
||
@return [
|
||
{
|
||
"Family": "ipv4",
|
||
"Address": "192.168.1.190",
|
||
"Strategy": "accept",
|
||
"Chain": "INPUT",
|
||
"Timeout": timeout
|
||
}
|
||
]
|
||
"""
|
||
if chain == "INPUT":
|
||
ipset_chain = "in"
|
||
elif chain == "OUTPUT":
|
||
ipset_chain = "out"
|
||
else:
|
||
return []
|
||
|
||
rules = []
|
||
try:
|
||
for strategy in ["accept", "drop"]:
|
||
ipset_cmd = "ipset list {}_bt_user_{}_ipset | awk '/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ && /timeout/ {{print $1, $3}}'".format(
|
||
ipset_chain, strategy
|
||
)
|
||
stdout, stderr = public.ExecShell(ipset_cmd)
|
||
iplist = stdout.split("\n")
|
||
|
||
for line in iplist:
|
||
if line == "": continue
|
||
address = line.strip().split(" ")[0]
|
||
timeout = line.strip().split(" ")[1]
|
||
rule = {
|
||
"Family": "ipv4",
|
||
"Address": address,
|
||
"Strategy": strategy,
|
||
"Chain": chain,
|
||
"Timeout": timeout
|
||
}
|
||
rules.append(rule)
|
||
return rules
|
||
except Exception as _:
|
||
return ""
|
||
|
||
def list_address(self, chains: list = None):
|
||
"""
|
||
获取指定链的ipset列表
|
||
:param chains: 链列表 ["INPUT","OUTPUT"]
|
||
"""
|
||
if chains is None:
|
||
chains = ["INPUT", "OUTPUT"]
|
||
result = []
|
||
for chain in chains:
|
||
result = result + self.get_chain_data(chain)
|
||
return result
|
||
|
||
def parse_rules(self, stdout):
|
||
"""
|
||
@name 解析规则列表输出,返回规则列表字典
|
||
@author wzz <2024/3/19 下午 3:53>
|
||
字段含义:
|
||
"number": 规则编号,对应规则在链中的顺序。
|
||
"chain": 规则所属的链的名称。
|
||
"pkts": 规则匹配的数据包数量。
|
||
"bytes": 规则匹配的数据包字节数。
|
||
"target": 规则的目标动作,表示数据包匹配到该规则后应该执行的操作。
|
||
"prot": 规则适用的协议类型。
|
||
"opt": 规则的选项,包括规则中使用的匹配条件或特定选项。
|
||
"in": 规则匹配的数据包的输入接口。
|
||
"out": 规则匹配的数据包的输出接口。
|
||
"source": 规则匹配的数据包的源地址。
|
||
"destination": 规则匹配的数据包的目标地址。
|
||
"options": 规则的其他选项或说明,通常是规则中的注释或附加信息。
|
||
|
||
protocol(port协议头中数字对应的协议类型):
|
||
0: 表示所有协议
|
||
1: ICMP(Internet 控制消息协议)
|
||
6: TCP(传输控制协议)
|
||
17: UDP(用户数据报协议)
|
||
@param "data":{"参数名":""} <数据类型> 参数描述
|
||
@return dict{"status":True/False,"msg":"提示信息"}
|
||
"""
|
||
lines = stdout.strip().split('\n')
|
||
rules = []
|
||
current_chain = None
|
||
for line in lines:
|
||
if line.startswith("Chain"):
|
||
current_chain = line.split()[1]
|
||
elif (line.startswith("target") or line.strip() == "" or "source" in line or
|
||
"Warning: iptables-legacy tables present" in line):
|
||
# 过滤表头,空行,警告
|
||
continue
|
||
else:
|
||
rule_info = line.split()
|
||
rule = {
|
||
"number": rule_info[0],
|
||
"chain": current_chain,
|
||
"pkts": rule_info[1],
|
||
"bytes": rule_info[2],
|
||
"target": rule_info[3],
|
||
"prot": rule_info[4],
|
||
"opt": rule_info[5],
|
||
"in": rule_info[6],
|
||
"out": rule_info[7],
|
||
"source": rule_info[8],
|
||
"destination": rule_info[9],
|
||
"options": " ".join(rule_info[10:]).strip()
|
||
}
|
||
rules.append(rule)
|
||
return rules
|
||
|
||
def list_rules(self, parm):
|
||
"""
|
||
@name 列出指定表的指定链的规则
|
||
@author wzz <2024/3/19 下午 3:02>
|
||
@param
|
||
@return
|
||
"""
|
||
try:
|
||
stdout = subprocess.check_output(
|
||
['iptables', '-t', parm['table'], '-L', parm['chain_name'], '-nv', '--line-numbers'],
|
||
stderr=subprocess.STDOUT, universal_newlines=True
|
||
)
|
||
return self.parse_rules(stdout)
|
||
except Exception as _:
|
||
return []
|
||
|
||
def list_port_forward(self):
|
||
"""
|
||
@name 调用list_rules获取所有nat表中的PREROUTING链的规则(端口转发规则),并分析成字典返回
|
||
@return dict{"status":True/False,"msg":"提示信息"}
|
||
"""
|
||
try:
|
||
port_forward_rules = self.list_rules({"table": "nat", "chain_name": "FORWARD_BT"})
|
||
rules = []
|
||
for rule in port_forward_rules:
|
||
rule_item = {
|
||
"type": "port_forward",
|
||
"number": rule["number"],
|
||
"S_Address": rule["source"],
|
||
"S_Port": "",
|
||
"T_Address": "",
|
||
"T_Port": "",
|
||
"Protocol": "TCP"
|
||
}
|
||
|
||
options = rule["options"].split(' ')
|
||
if rule["target"] == "DNAT": # 外部转发 有IP
|
||
rule_item["S_Port"] = options[1].split("dpt:")[1]
|
||
rule_item["T_Address"] = options[2].split("to:")[1].split(":")[0]
|
||
rule_item["T_Port"] = options[2].split("to:")[1].split(":")[1]
|
||
elif rule["target"] == "REDIRECT": # 内部转发无IP
|
||
rule_item["S_Port"] = options[1].split("dpt:")[1]
|
||
rule_item["T_Address"] = "127.0.0.1"
|
||
rule_item["T_Port"] = options[-1]
|
||
else:
|
||
continue
|
||
|
||
if rule["prot"] == "6" or rule["prot"] == "tcp":
|
||
rule_item["Protocol"] = "TCP"
|
||
elif rule["prot"] == "17" or rule["prot"] == "udp":
|
||
rule_item["Protocol"] = "UDP"
|
||
|
||
rules.append(rule_item)
|
||
return rules
|
||
except Exception as _:
|
||
return []
|
||
|
||
def port_forward(self, info, operation):
|
||
"""
|
||
设置端口转发规则
|
||
:param info: 规则信息
|
||
:param operation: 操作类型 add/del
|
||
"""
|
||
to_addr = info["T_Address"]
|
||
|
||
if operation == "add":
|
||
operation = "A"
|
||
else:
|
||
operation = "D"
|
||
|
||
is_lo = False
|
||
if to_addr == "" or to_addr == "0.0.0.0" or to_addr == "127.0.0.1" or to_addr == "0.0.0.0/0":
|
||
is_lo = True
|
||
|
||
if is_lo:
|
||
exec_cmd = "iptables -t nat -{operation} FORWARD_BT -p {proto} --dport {sport} -j REDIRECT --to-port {tport}".format(
|
||
operation=operation, proto=info["Protocol"], sport=info["S_Port"], tport=info["T_Port"])
|
||
else:
|
||
exec_cmd = "iptables -t nat -{operation} FORWARD_BT -p {proto} --dport {sport} -j DNAT --to-destination {taddr}:{tport}".format(
|
||
operation=operation, proto=info["Protocol"], addr=info["S_Address"], sport=info["S_Port"],
|
||
taddr=to_addr, tport=info["T_Port"])
|
||
|
||
stdout, stderr = public.ExecShell(exec_cmd)
|
||
if stderr:
|
||
return self._result(False, public.lang(f"Failed to set up rule:{stderr}"))
|
||
public.ExecShell("systemctl reload BT-FirewallServices")
|
||
return self._result(True, public.lang("Setting up the rule was successful"))
|