全局常开 proxifier 的规则生成和配置笔记
配置文件位置
proxifier
的配置文件位置在 C:\Users\[changanmatu]\AppData\Roaming\Proxifier4\Profiles\xxxx.ppx
,具体是哪个文件以界面设置为准。
全局常开用法
proxifier
一般有两种使用模式,一种是需要个别用的时候开一下,把需要用的程序加入规则,用完关掉。这个时候其他程序能不能正常访问网络并不重要。
另一种是全局常开,一般思路是把各种不需要proxifier
的访问行为携程规则,设置direct
,然后最后留一条全局的,导向某个服务器。这个用法虽然理论上省心,但实际上前置direct
规则的设置不是那么容易。
我的思路是,将前置的这些白名单分成三类:
- 最优先的,可以确定某些
APP
一定不需要转向的,例如国产全家桶、自建服务的客户端、NAS客户端、转向程序本身等。这些可以依据程序路径写成通配符; - 第二优先的,国内知名网站的网址及其CDN地址,这个列表就讲究了,后面详说;
- 第三,基本上兜底的,GeoIP数据表;这个列表非常大又经常更新,必须程序处理;
显然,这个设置不会是一劳永逸的,至少GeoIP数据表需要定期更新。这需要找到一个更新源头,例如苍狼山庄的数据。
ppx
文件实际上是XML
文本文件,不仅控制着转向规则,也控制着大部分界面配置数据,我们可以在GUI上设置好大部分杂项,然后用记事本留下一些标记,最后用一个自编程序,用文本替换的方法生成最终的ppx
文件。
做为示例,配置文件模板可能是这样的:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ProxifierProfile version="102" platform="Windows" product_id="0" product_minver="400">
<Options>
<Resolve>
<AutoModeDetection enabled="true" />
<ViaProxy enabled="true" />
<BlockNonATypes enabled="false" />
<ExclusionList OnlyFromListMode="false">%ComputerName%; localhost; *.local </ExclusionList>
<DnsUdpMode>0</DnsUdpMode>
</Resolve>
<Encryption mode="basic" />
<ConnectionLoopDetection enabled="true" resolve="true" />
<Udp mode="mode_bypass" />
<LeakPreventionMode enabled="false" />
<ProcessOtherUsers enabled="false" />
<ProcessServices enabled="false" />
<HandleDirectConnections enabled="false" />
<HttpProxiesSupport enabled="false" />
</Options>
<ProxyList>
<Proxy id="100" type="SOCKS5">
<Options>304</Options>
<Port>你的端口号</Port>
<Address>你的IP</Address>
<Label>注释名字</Label>
</Proxy>
</ProxyList>
<ChainList />
<RuleList>
<Rule enabled="true">
<Action type="Direct" />
<Targets>localhost; 127.0.0.1; %ComputerName%; ::1</Targets>
<Name>Localhost</Name>
</Rule>
<Rule enabled="true">
<Action type="Direct" />
<Applications>C:\APP\EasyTierSrv\*.exe;C:\APP\Clash.Nyanpasu\*.exe;qsync.exe;wechat*.exe;sogou*.exe;baidu*.exe;tencent*.exe;qq*.exe;ssh.exe</Applications>
<Name>APP白名单</Name>
</Rule>
<Rule enabled="true">
<Action type="Direct" />
<Targets>10.10.10.*;192.168.*;*.weiran.ink</Targets>
<Name>私网白名单</Name>
</Rule>
###MY_TARGET_RULE###
<Rule enabled="false">
<Action type="Direct" />
<Targets>*:*:*:*:*:*:*:*;0:0:0:0:0:0:0:0-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff</Targets>
<Name>IPv6白名单</Name>
</Rule>
<Rule enabled="true">
<Action type="Proxy">100</Action>
<Name>Default</Name>
</Rule>
</RuleList>
</ProxifierProfile>
显然,其中###MY_TARGET_RULE###
的位置就是需要我们自己生成的规则位置。这里同时包含了IP和网址规则。由于条目比较多,必须分散来写,这个任务我是靠python完成了:
#!/usr/bin/python3
import requests
import ipaddress
import logging
# 设置日志记录
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
# 定义扩展后的程序
def cidr_to_ip_range(cidr):
"""将CIDR转换为IP范围"""
try:
network = ipaddress.ip_network(cidr, strict=False)
first_ip = network.network_address + 1
last_ip = network.broadcast_address - 1
return f"{first_ip}-{last_ip}"
except ValueError as e:
logging.error(f"Invalid CIDR format: {cidr}. Error: {e}")
return None
def get_and_convert_cidr(cidr_src):
"""获取并转换CIDR"""
try:
cidr_text = None
if cidr_src.startswith("http"):
response = requests.get(cidr_src)
response.raise_for_status() # 检查请求是否成功
cidr_text = response.text
else:
with open(cidr_src, "r", encoding="utf-8") as f:
cidr_text = f.read()
cidr_lines = cidr_text.splitlines()
ip_ranges = [
cidr_to_ip_range(line.strip()) for line in cidr_lines if line.strip()
]
return ip_ranges
except requests.RequestException as e:
logging.error(f"Error fetching CIDR data: {e}")
except FileNotFoundError as e:
logging.error(f"File not found: {e}")
except Exception as e:
logging.error(f"An error occurred: {e}")
def group_ip_ranges(ip_ranges, group_size=512):
"""将IP范围分组"""
return [ip_ranges[i : i + group_size] for i in range(0, len(ip_ranges), group_size)]
def format_target_groups_to_rules(target_groups, rule_title):
"""将目标文本组格式化为规则"""
rules = []
for index, group in enumerate(target_groups, start=1):
ip_range_str = ";".join(filter(None, group)) # 过滤掉None值
rule_template = f"""\t\t<Rule enabled="true">
\t\t\t<Action type="Direct" />
\t\t\t<Targets>{ip_range_str}</Targets>
\t\t\t<Name>{rule_title}-{index}</Name>
\t\t</Rule>"""
rules.append(rule_template)
return rules
def read_and_filter_domains(domain_file):
"""读取并过滤域名文件,返回星号开头的域名列表"""
domain_list = []
try:
with open(domain_file, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if line.startswith("*"):
domain_list.append(line)
return domain_list
except FileNotFoundError as e:
logging.error(f"File not found: {e}")
except Exception as e:
logging.error(f"An error occurred while reading the domain file: {e}")
def group_domains(domains, group_size=256):
"""将域名分组"""
return [domains[i : i + group_size] for i in range(0, len(domains), group_size)]
def format_domain_groups_to_rules(domain_groups):
"""将域名组格式化为规则"""
rules = []
for index, group in enumerate(domain_groups, start=1):
domain_str = ";".join(group)
rule_template = f"""\t\t<Rule enabled="true">
\t\t\t<Action type="Direct" />
\t\t\t<Targets>{domain_str}</Targets>
\t\t\t<Name>CHINA-DOMAIN-{index}</Name>
\t\t</Rule>"""
rules.append(rule_template)
return rules
def update_rule_file(
rule_text_list, src_filepath="Default.ppx.template", dst_filepath="MyProfile.ppx"
):
"""更新规则文件"""
try:
final_txt = "\n".join(filter(None, rule_text_list)) # 过滤掉None值
with open(src_filepath, "r", encoding="utf-8") as f:
template_text = f.read()
rule_file_text = template_text.replace("###MY_TARGET_RULE###", final_txt)
with open(dst_filepath, "w", encoding="utf-8") as f:
f.write(rule_file_text)
logging.info(f"Rule file updated successfully: {dst_filepath}")
except FileNotFoundError as e:
logging.error(f"File not found: {e}")
except Exception as e:
logging.error(f"An error occurred while updating the rule file: {e}")
if __name__ == "__main__":
rules = []
# 附加域名白名单
# 自己需要的比较固定的白名单,只有域名,IP应该不会经常改,需要的话写模板中
domain_file = "allow-list-ext.txt"
domains = read_and_filter_domains(domain_file)
if domains:
my_domain_groups = group_domains(domains)
my_domain_rules = format_target_groups_to_rules(my_domain_groups, "我的网址")
# 将域名规则添加到最终的规则列表中
rules.extend(my_domain_rules)
# 域名白名单
# https://raw.gayhubusercontent.com/pluwen/china-domain-allowlist/refs/heads/main/allow-list.sorl
domain_file = "allow-list.sorl"
domains = read_and_filter_domains(domain_file)
if domains:
my_domain_groups = group_domains(domains)
my_domain_rules = format_target_groups_to_rules(
my_domain_groups, "CHINA-DOMAIN"
)
# 将域名规则添加到最终的规则列表中
rules.extend(my_domain_rules)
# IP 白名单
cidr_src = "https://ispip.clang.cn/all_cn_cidr.txt"
# cidr_src = 'all_cn_cidr.txt'
ip_ranges = get_and_convert_cidr(cidr_src)
if ip_ranges:
ip_groups = group_ip_ranges(ip_ranges)
ip_rules = format_target_groups_to_rules(ip_groups, "CHINA-IP")
rules.extend(ip_rules)
# 更新规则文件
update_rule_file(rules)
这个脚本直接从苍狼山庄下载的逐行CIDR格式的IP地址段,然后进行分组,最后将规则写入到PPX文件中去。
同时,读取一个allow-list.sorl
文件,里面是域名白名单,然后进行分组,最后将也规则写入到PPX文件中去。allow-list.sorl
文件可以找别人整理的域名列表,这个不宜太大,不然PPX文件太大;同时也不必要,因为不常用的空绕一圈也没关系。由于更新的不频繁,同时网络访问不变,这个设计的是提前自己下载放在那里。
还有一个文件是allow-list-ext.txt
,这个是附加的域名白名单,这个可以自己添加一些域名,比如自建的服务等。这个与写模板中是等价的,但有时候用起来方便点。我是这样写的:
# 自己需要的比较固定的白名单,只有域名,IP应该不会经常改,需要的话写模板中
*.weiran.ink
*.bing.com
*.mycloudnas.com
*.baidu*.com
*.kosoft.com
# bilibili全家桶
*.bili*.com
*.szbdyd.com
*.shanghaihuanli.com
*.aliyun*.com
*.clang.cn
运行,就生成了一份可以在proxifier
的 GUI 中加载的ppx
文件。
自动更新与自动更新
上面的东西中,IP是频繁更新的。一个方法是把上面一套东西就放在C:\Users\[changanmatu]\AppData\Roaming\Proxifier4\Profiles\
,自己经常手动运行一下。
更高级的用法就是拿个VPS定时运行,然后提供一个文件服务,让 proxifier
每次启动时自动下载这个文件。自动更新功能在File
菜单下,就不展开了。
如果是用cron
定时运行,则必须写一个包装脚本,因为cron
自身不能cd
到存放这些零碎文件的目录中。
#!/usr/bin/env bash
cd "$(dirname "$0")"
/usr/bin/python3 updateRule.py
cron
的配置如下:
0 5 * * * /bin/bash /var/www/proxifier/updateRuleCronWrapper.bash
尤其适合有好几台电脑的人。
ipv6,DNS 与 bilibili
我的环境中有ipv6,之前没怎么用,也相安无事。但使用上面的模式后暴露了一些问题:
- 最终也没能找到在
proxifier
中匹配ipv6地址的写法。上面模板中是涉及了ipv6地址,proxifier
也自称支持,但实际上那个规则永远都不能生效(所以最后把他disable了); - 在启用ipv6的状态下,很多域名会优先解析到ipv6地址,而
proxifier
中没有ipv6的规则,所以这些访问统统被转向了;而我这边的转向软件对ipv6的处理不怎么好,或者是ipv6环境自身不稳定,最后就变成了整个网络很不稳定,大量报错日志; - 网络有人建议改用
8.8.8.8
DNS,稍有改善,但没有解决全部问题;最终权衡之后,我关掉了网络适配器中的ipv6协议,用纯ipv4网络,基本恢复了正常。
几天之后发现,bilibili访问速度及其之慢,基本上没法访问。控制台调试无果,最后搜了一下发现可能是B站直接在海外屏蔽了DNS。把DNS改回来变成自动获取,速度恢复正常,其他网站目前看也没有异常。这一事实又带来了两个新的结论:
- 众所周知国内的
8.8.8.8
是不纯洁的,但显然不纯洁的程度没那么高,起码,纯洁版解析不出来的东西,不纯洁版也解析不出来; - 上面的
ppx
模板中,同时开启AutoModeDetection
和ViaProxy
两个选项,效果是比较好的,proxifier
会根据情况作出正确判断;
vscode 的 autopep8
proxifier
有一个 HandleDirectConnections
,也就是接管直连链接的功能,可以把那些走direct
的链接都接管,虽然不转向,但参与显示和速度统计,方便调试和统计。对应于上面模板的HandleDirectConnections
字段。
不过,这玩意儿跟autopep8
这个python模块有点冲突。vscode
与autopep8
是依靠网络接口通讯的,不知道涉及了什么黑科技,开着proxifier
并且开启了HandleDirectConnections
的情况下,这二者是无法连接的。
也有可能与conda
环境有关,出问题的是装在conda
中的一个python环境,不知道是否有一些路径映射问题,因为conda
会在路径访问上搞一些黑科技,而proxifier
中是讲究程序的路径的,不单纯是网络层的操作(否则也不致于可以按APP路径设置白名单)。
P.S. 现在是2024年11月6日15:22:39,刚刚传来的消息说床破陛下将于晚些时候抵达他忠实的白宫。