CIDR只保留构建树所需的前缀位,减少最终文件体积约100k;树底层使用map替代object提高大约7倍查询效率(实际单次查询差距绝对值在0.002ms以下,无意义的优化)

This commit is contained in:
zhiyi
2024-12-04 17:02:21 +08:00
parent 509f516d1d
commit 74e6d0aa47
5 changed files with 60 additions and 86 deletions

View File

@@ -11,6 +11,7 @@ on:
- 'direct-domains.txt' - 'direct-domains.txt'
- 'proxy-domains.txt' - 'proxy-domains.txt'
- 'cidrs-cn.txt' - 'cidrs-cn.txt'
- '!gfw.pac'
workflow_dispatch: workflow_dispatch:
jobs: jobs:

2
.gitignore vendored
View File

@@ -43,3 +43,5 @@ $RECYCLE.BIN/
# Windows shortcuts # Windows shortcuts
*.lnk *.lnk
test.js

View File

@@ -27,16 +27,11 @@ def parse_args():
def convert_cidr(cidr): def convert_cidr(cidr):
if '/' in cidr: if '/' in cidr:
network = ipaddress.ip_network(cidr.strip(), strict=False) network = ipaddress.ip_network(cidr.strip(), strict=False)
network_address = network.network_address network_address = int(network.network_address) >> (network.max_prefixlen - network.prefixlen)
prefixlen = network.prefixlen
else: else:
network = ipaddress.ip_address(cidr.strip()) network = ipaddress.ip_address(cidr.strip())
network_address = network network_address = network
prefixlen = network.max_prefixlen return hex(int(network_address))[2:]
if network.version == 4:
return hex(int(network_address))[2:] + '/' + str(prefixlen)
else:
return network.compressed
def generate_cnip_cidrs(): def generate_cnip_cidrs():
""" 从文件中读取CIDR地址 """ """ 从文件中读取CIDR地址 """

68
gfw.pac
View File

File diff suppressed because one or more lines are too long

View File

@@ -10,35 +10,45 @@ var localTlds = __LOCAL_TLDS__;
var cidrs = __CIDRS__; var cidrs = __CIDRS__;
var hasOwnProperty = Object.hasOwnProperty;
function isIpAddress(ip) { function isIpAddress(ip) {
return /^\d{1,3}(\.\d{1,3}){3}$/.test(ip) || /^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$/.test(ip); return /^\d{1,3}(\.\d{1,3}){3}$/.test(ip) || /^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$/.test(ip);
} }
function RadixTree() { function RadixTree() {
this.root = {}; this.root = new Map();
} }
RadixTree.prototype.insert = function(string) { RadixTree.prototype.insert = function(string) {
var node = this.root; var node = this.root;
for (var i = 0; i < string.length; i++) { for (var i = 0; i < string.length; i++) {
var char = string[i]; var char = string[i];
if (!node[char]) { if (!node.has(char)) {
node[char] = {}; node.set(char, new Map());
} }
node = node[char]; node = node.get(char);
} }
}; };
RadixTree.prototype.to_list = function() { RadixTree.prototype.search = function(string) {
return this.root; var currentNode = this.root;
}; var isLastNode = false;
for (var i=0; i < string.length; i++) {
var char = string[i];
if (currentNode.has(char)) {
currentNode = currentNode.get(char);
isLastNode = currentNode.size === 0;
} else {
break;
}
}
return isLastNode;
}
function ipToBinary(ip) { function ipToBinary(ip) {
var bin = ''
// Check if it's IPv4 // Check if it's IPv4
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) { if (/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) {
return ip.split('.').map(function(num) { bin = ip.split('.').map(function(num) {
return ("00000000" + parseInt(num, 10).toString(2)).slice(-8); return ("00000000" + parseInt(num, 10).toString(2)).slice(-8);
}).join(''); }).join('');
} else if (/^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$/.test(ip)) { } else if (/^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$/.test(ip)) {
@@ -54,25 +64,11 @@ function ipToBinary(ip) {
var fullAddress = left.concat(Array(zeroGroups + 1).join('0').split('')).concat(right); var fullAddress = left.concat(Array(zeroGroups + 1).join('0').split('')).concat(right);
// Convert each group to binary and pad to 16 bits // Convert each group to binary and pad to 16 bits
return fullAddress.map(function(group) { bin = fullAddress.map(function(group) {
return ("0000000000000000" + parseInt(group || '0', 16).toString(2)).slice(-16); return ("0000000000000000" + parseInt(group || '0', 16).toString(2)).slice(-16);
}).join(''); }).join('');
} }
} return bin.replace(/^0+/, '');
function searchRadixTree(bits) {
var currentNode = radixTree;
var isLastNode = false;
for (var i=0; i<bits.length; i++) {
var char = bits[i];
if (currentNode[char]) {
currentNode = currentNode[char];
isLastNode = Object.keys(currentNode).length === 0;
} else {
break;
}
}
return isLastNode;
} }
function isInDirectDomain(host) { function isInDirectDomain(host) {
@@ -142,7 +138,7 @@ function FindProxyForURL(url, host) {
} else if (isPrivateIp(ip)) { } else if (isPrivateIp(ip)) {
debug('域名解析后命中私有 IP 地址', host, ip); debug('域名解析后命中私有 IP 地址', host, ip);
return direct; return direct;
} else if (searchRadixTree(ipToBinary(ip))) { } else if (radixTree.search(ipToBinary(ip))) {
debug('匹配到直连IP', host, ip); debug('匹配到直连IP', host, ip);
return direct; return direct;
} }
@@ -152,7 +148,7 @@ function FindProxyForURL(url, host) {
} }
var allowAlert = true var allowAlert = true
function debug(msg, host, ip) { function debug(msg, host='', ip='') {
if (!allowAlert) { if (!allowAlert) {
return return
} }
@@ -166,19 +162,11 @@ function debug(msg, host, ip) {
var radixTree = new RadixTree(); var radixTree = new RadixTree();
(function () { (function () {
var startTime = new Date().getMilliseconds(); debug('开始生成 Radix Tree', 'PAC文件载入开始');
debug('开始生成 Radix Tree', 'PAC文件载入开始', startTime.toString());
for (let i=0; i<cidrs.length; i++) { for (let i=0; i<cidrs.length; i++) {
var cidr = cidrs[i]; var prefix = cidrs[i];
var [ip, prefixLen] = cidr.split('/'); var bits = (parseInt(prefix, 16)).toString(2);
if (!cidr.includes(':')) {
var ip = ip.match(/.{1,2}/g).map(function(byte) {
return parseInt(byte, 16);
}).join('.');
}
var bits = ipToBinary(ip).slice(0, prefixLen);
radixTree.insert(bits); radixTree.insert(bits);
} }
radixTree = radixTree.to_list();
debug('Radix Tree 已生成', 'PAC文件载入完毕', cidrs.length.toString()+'个CIDR条目'); debug('Radix Tree 已生成', 'PAC文件载入完毕', cidrs.length.toString()+'个CIDR条目');
})(); })();