diff --git a/README.md b/README.md index a61f4c2..07d275d 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ PAC scripts for proxies ## 使用 -获取方式:[本仓库的 Releases](https://github.com/iBug/pac/releases/latest) +获取方式:[本仓库的 Releases](https://github.com/wits-fe/pac/releases/latest) - `pac-.txt` 包含从数据源 `` 获取的 IP 地址列表(白名单) - `pac-gfwlist-.txt` 在 IP 白名单的基础上添加了 GFWList 的匹配 diff --git a/build.py b/build.py index 802d7a3..a970214 100755 --- a/build.py +++ b/build.py @@ -8,11 +8,16 @@ from requests.exceptions import RequestException, HTTPError import gfwlist - -SOURCES = { +SOURCES_4 = { 'ipdeny.com': 'http://www.ipdeny.com/ipblocks/data/aggregated/cn-aggregated.zone', '17mon': 'https://raw.githubusercontent.com/17mon/china_ip_list/master/china_ip_list.txt', } +SOURCES_6 = { + 'gaoyifan': 'https://gaoyifan.github.io/china-operator-ip/china6.txt', +} +SOURCES_46 = { + 'maxmind': 'https://github.com/v2fly/geoip/raw/release/text/cn.txt', +} OUT_DIR = "dist" # Stub content to disable GFWList check @@ -25,6 +30,8 @@ def fetch_and_convert(src): template = "var CHINA = [\n{}\n];\n" lines = [] for iprange in response.text.strip().split("\n"): + if iprange.find(":") != -1: + break ipnet = ipaddress.IPv4Network(iprange) netaddr = int(ipnet.network_address) netmask = int(ipnet.netmask) @@ -34,6 +41,101 @@ def fetch_and_convert(src): return template.format("\n".join(lines)) +def fetch_and_convert_ip6(src): + response = requests.get(src) + response.raise_for_status() + text = response.text + + template = "var CHINA6_F = [\n{}\n];\n\n" + template2 = "var CHINA6_S = [\n{}\n];\n" + lines = [] + lines2 = [] + lastnum = 0 + count = 0 + begins = False + ends = False + lower = 0 + upper = 0 + networkstr_b = "" + iprangestr_b = "" + fixlen = len(f" [0xFFFFFFFF, -1, 0xFFFFFFFF],") + + for iprange in text.strip().split("\n"): + if iprange.find(":") == -1: + continue + ipnet = ipaddress.IPv6Network(iprange) + prefixlen = ipnet.prefixlen + fulladdr = str(ipnet.exploded).replace(':', '') + num1 = int(fulladdr[0:8], 16) + num2 = int(fulladdr[8:16], 16) + fullmask = f"{ipnet.netmask:X}" + mask1 = fullmask[0:8] + mask2 = fullmask[8:16] + + if lastnum != num1 and begins: + ends = True + + if ends: + begins = False + ends = False + upper = count - 1 + s2 = networkstr_b + if upper == lower: + s2 = iprangestr_b + s = f" [0x{lastnum:08X}, {lower}, {upper}]," + len1 = len(s) + if len1 < fixlen: + s = s + " " * (fixlen - len1) + s = f"{s} // {s2}" + lines.append(s) + + if prefixlen <= 32: + if begins: + raise NameError(f"Invalid list order: \n{iprange}") + s = f" [0x{num1:08X}, -1, 0x{mask1}], // {iprange}" + lines.append(s) + else: + if not begins: + begins = True + lower = count + networkstr_b = str(ipnet.exploded)[0:10] + iprangestr_b = iprange + + s = f" [0x{num2:08X}, 0x{mask2}], // {count}, {iprange}" + lines2.append(s) + count = count + 1 + lastnum = num1 + + if begins: + begins = False + ends = False + upper = count - 1 + s2 = networkstr_b + if upper == lower: + s2 = iprangestr_b + s = f" [0x{lastnum:08X}, {lower}, {upper}]," + len1 = len(s) + if len1 < fixlen: + s = s + " " * (fixlen - len1) + s = f"{s} // {s2}" + lines.append(s) + + lines.append(" [0xFFFFFFFF, -1, 0xFFFFFFFF] // ffff:ffff::/32 placeholder") + lines2.append(f" [0xFFFFFFFF, 0xFFFFFFFF] // {count}, placeholder, not in use") + + return template.format("\n".join(lines)) + template2.format("\n".join(lines2)) + + +def write_pac(filename, code, data, data_6, tail): + with open(os.path.join(OUT_DIR, filename), "w") as f: + f.write(code) + f.write(data) + f.write("\n") + f.write(data_6) + f.write("\n") + f.write(tail) + + def main(): now = datetime.utcnow() date = now.strftime("%Y%m%d") @@ -45,27 +147,37 @@ def main(): gfwlist_stub = GFWLIST_STUB os.makedirs(OUT_DIR, mode=0o755, exist_ok=True) - for key in SOURCES: - print(f"Generating PAC script from source {key}") + for key in SOURCES_4: + key_6 = list(SOURCES_6)[0] + print(f"Generating PAC script from source {key}(IPv4) & {key_6}(IPv6)") try: - data = fetch_and_convert(SOURCES[key]) + data = fetch_and_convert(SOURCES_4[key]) + data_6 = fetch_and_convert_ip6(SOURCES_6[key_6]) except RequestException: continue except HTTPError: continue - filename = f"pac-{key}.txt" - filename_gfwlist = f"pac-gfwlist-{key}.txt" - with open(os.path.join(OUT_DIR, filename), "w") as f: - f.write(code) - f.write(data) - f.write("\n") - f.write(gfwlist_stub) - with open(os.path.join(OUT_DIR, filename_gfwlist), "w") as f: - f.write(code) - f.write(data) - f.write("\n") - f.write(gfwlist_part) + filename = f"pac-IPv4_{key}--IPv6_{key_6}.txt" + filename_gfwlist = f"pac-gfwlist-IPv4_{key}--IPv6_{key_6}.txt" + write_pac(filename, code, data, data_6, gfwlist_stub) + write_pac(filename_gfwlist, code, data, data_6, gfwlist_part) + + for key in SOURCES_46: + print(f"Generating PAC script from source {key}(IPv4v6)") + try: + data = fetch_and_convert(SOURCES_46[key]) + data_6 = fetch_and_convert_ip6(SOURCES_46[key]) + except RequestException: + continue + except HTTPError: + continue + + filename = f"pac-IPv4v6_{key}.txt" + filename_gfwlist = f"pac-gfwlist-IPv4v6_{key}.txt" + write_pac(filename, code, data, data_6, gfwlist_stub) + write_pac(filename_gfwlist, code, data, data_6, gfwlist_part) + if __name__ == '__main__': diff --git a/code.js b/code.js index 06c3388..ebedab0 100644 --- a/code.js +++ b/code.js @@ -5,8 +5,100 @@ var proxy = __PROXY__; var direct = "DIRECT"; +// lower: lower_index +// upper: (upper_index + 1) / (array_length if upper_index = last) +function binarySearch(list, num, lower, upper) { + var x = lower, y = upper, middle; + while (y - x > 1) { + middle = Math.floor((x + y) / 2); + if (list[middle][0] > num) + y = middle; + else + x = middle; + } + return x; +} + +function convertToUInt6(high, low) { + var num1 = parseInt(high, 16) & 0xFFFF; + var num2 = parseInt(low, 16) & 0xFFFF; + return (((num1 << 16) | num2) >>> 0); +} + +function isInNet6(parts, list, list2) { + var num = convertToUInt6(parts[0], parts[1]); + var x = binarySearch(list, num, 0, list.length); + + if (list[x][1] == -1) + return (((num & list[x][2]) ^ list[x][0]) === 0); + + // not in net (/33 - /64) + if (num !== list[x][0]) + return false; + + var num2 = convertToUInt6(parts[2], parts[3]); + var x2 = binarySearch(list2, num2, list[x][1], list[x][2] + 1); + + return (((num2 & list2[x2][1]) ^ list2[x2][0]) === 0); +} + +function isLanOrChina6(host) { + var addr = host; + if (addr.indexOf("[") !== -1) { + addr = addr.substring(1, addr.length - 1); + } + + if (addr.indexOf("::") === -1) { + var groups = addr.split(":"); + if (groups.length != 8) + return false; // invalid ipv6 format + return isInNet6(groups, CHINA6_F, CHINA6_S) || isInNet6(groups, LAN6_F, LAN6_S); + } + + var halfs = addr.split("::"); + var left = halfs[0]; + var right = halfs[1]; + if (left.length < 1) left = "0000"; + if (right.length < 1) right = "0000"; + + var groups1 = left.split(":"); + if (groups1.length > 3) + return isInNet6(groups1, CHINA6_F, CHINA6_S) || isInNet6(groups1, LAN6_F, LAN6_S); + + var groups2 = right.split(":"); + var zeros = 8 - (groups1.length + groups2.length); + if (zeros < 2) + return false; // invalid ipv6 format + + var parts = ["0", "0", "0", "0"]; + parts[0] = groups1[0]; + var i = 1; + for (var j = 1; j < groups1.length; j++) { + parts[i] = groups1[j]; + i = i + 1; + if (i == 4) break; + } + + if (i < 4) { + for (var k = 0; k < zeros; k++) { + parts[i] = "0000"; + i = i + 1; + if (i == 4) break; + } + } + if (i == 3) parts[3] = groups2[0]; + + return isInNet6(parts, CHINA6_F, CHINA6_S) || isInNet6(parts, LAN6_F, LAN6_S); +} + +function convertToUInt(host) { + var bytes = host.split("."); + var result = ((bytes[0] & 0xFF) << 24) | ((bytes[1] & 0xFF) << 16) | ((bytes[2] & 0xFF) << 8) | (bytes[3] & 0xFF); + return (result >>> 0); +} + function belongsToSubnet(host, list) { - var ip = convert_addr(host) >>> 0; + var ip = convertToUInt(host); if (list.length === 0 || ip < list[0][0]) return false; @@ -83,11 +175,35 @@ function FindProxyForURL(url, host) { } // Fallback to IP whitelist - var remote = dnsResolve(host); - if (!remote || remote.indexOf(":") !== -1) { - // resolution failed or is IPv6 addr + + // if host is IPv6 + if (host.indexOf(":") !== -1) { + if (isLanOrChina6(host)) { + return direct; + } return proxy; } + + var remote; + if(typeof dnsResolveEx == 'function') { + remote = dnsResolveEx(host); + } else { + remote = dnsResolve(host); + } + + if (!remote) { + return proxy; + } else { + remote = remote.split(";")[0]; + } + + if (remote.indexOf(":") !== -1) { + if (isLanOrChina6(remote)) { + return direct; + } + return proxy; + } + if (isLan(remote) || isChina(remote)) { return direct; } @@ -104,3 +220,21 @@ var LAN = [ [0xC0A80000, 0xFFFF0000] // 192.168.0.0/16 ]; +// not support /65 - /128 +var LAN6_F = [ + [0x00000000, 0, 0], // ::/64 + [0x0064FF9B, 1, 2], // 64:ff9b: + [0x01000000, 3, 3], // 100::/64 + [0x20010000, -1, 0xFFFFFFFF], // 2001::/32 - teredo, may remove + [0xFC000000, -1, 0xFE000000], // fc00::/7 + [0xFE800000, -1, 0xFFC00000], // fe80::/10 + [0xFF000000, -1, 0xFF000000] // ff00::/8 +]; + +var LAN6_S = [ + [0x00000000, 0xFFFFFFFF], // 0, ::/64 - catch {::, ::1, ::ffff:0:0/96, ::ffff:0:0:0/96}, may remove + [0x00000000, 0xFFFFFFFF], // 1, 64:ff9b::/64 - catch {64:ff9b::/96, NAT64}, may remove + [0x00010000, 0xFFFF0000], // 2, 64:ff9b:1::/48 - NAT64, may remove + [0x00000000, 0xFFFFFFFF] // 3, 100::/64 +]; +