diff --git a/build.py b/build.py index 802d7a3..07e71dc 100755 --- a/build.py +++ b/build.py @@ -13,6 +13,9 @@ SOURCES = { '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', } +SOURCES2 = { + 'gaoyifan': 'https://gaoyifan.github.io/china-operator-ip/china6.txt', +} OUT_DIR = "dist" # Stub content to disable GFWList check @@ -34,6 +37,89 @@ 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"): + 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 main(): now = datetime.utcnow() date = now.strftime("%Y%m%d") @@ -49,22 +135,28 @@ def main(): print(f"Generating PAC script from source {key}") try: data = fetch_and_convert(SOURCES[key]) + key2 = list(SOURCES2)[0] + data2 = fetch_and_convert_ip6(SOURCES2[key2]) except RequestException: continue except HTTPError: continue - filename = f"pac-{key}.txt" - filename_gfwlist = f"pac-gfwlist-{key}.txt" + filename = f"pac-{key}-{key2}.txt" + filename_gfwlist = f"pac-gfwlist-{key}-{key2}.txt" with open(os.path.join(OUT_DIR, filename), "w") as f: f.write(code) f.write(data) f.write("\n") + f.write(data2) + 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(data2) + f.write("\n") f.write(gfwlist_part) diff --git a/code.js b/code.js index 06c3388..7d3de02 100644 --- a/code.js +++ b/code.js @@ -5,6 +5,102 @@ 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 fixLength(text) { + if (text.length == 1) return "000" + text; + if (text.length == 2) return "00" + text; + if (text.length == 3) return "0" + text; + return text; +} + +function convertToInt(high, low) { + if (low.length != 4) + return parseInt(high + fixLength(low), 16); + return parseInt(high + low, 16); +} + +function isLan_Internal6(parts) { + var num = convertToInt(parts[0], parts[1]); + var list = LAN6; + var x = binarySearch(list, num, 0, list.length); + + return ((num & list[x][1]) === list[x][0]); +} + +function isInNet6(parts, list, list2) { + var num = convertToInt(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]); + + // not in net (/33 - /64) + if (num !== list[x][0]) + return false; + + var num2 = convertToInt(parts[2], parts[3]); + var x2 = binarySearch(list2, num2, list[x][1], list[x][2] + 1); + + return ((num2 & list2[x2][1]) === list2[x2][0]); +} + +function isLanOrChina6(host) { + if (host.indexOf("::") === -1) { + var groups = host.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 = host.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 belongsToSubnet(host, list) { var ip = convert_addr(host) >>> 0; @@ -83,11 +179,34 @@ 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; } + + // default resolve IPv4 + // var remote = dnsResolve(host); + // if (!remote) { + // // resolution failed + // return proxy; + // } + + // method for IPv6 + var ips = dnsResolveEx(host); + if (!ips) + return proxy; + var remote = ips.split(";")[0]; + if (remote.indexOf(":") !== -1) { + if (isLanOrChina6(remote)) { + return direct; + } + return proxy; + } + if (isLan(remote) || isChina(remote)) { return direct; } @@ -104,3 +223,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 +]; +