diff --git a/.gitignore b/.gitignore index 5719d80..6a1390f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,4 @@ .DS_Store -china.txt -china6.txt -gfwlist.txt -auto-proxy.txt -domain-rules-*.txt +auto-proxy*.txt +*-rules-*.txt proxy.pac diff --git a/README.md b/README.md index 8173413..820759e 100644 --- a/README.md +++ b/README.md @@ -8,25 +8,46 @@ 项目包含一些示例配置文件: + - `auto-proxy.txt.example` - `domain-rules-blocked.txt.example` - `domain-rules-direct.txt.example` - `domain-rules-proxy.txt.example` + - `ipv4-rules-direct.txt.example` + - `ipv6-rules-direct.txt.example` 要使用这些文件,去掉 `.example` 扩展名。每个文件代表不同的代理行为: + - **Auto-Proxy 配置的规则**:把规则添加到 `auto-proxy.txt` 中,将会按照规则来访问网站。 + + 所有以 `auto-proxy` 开头,并且以 `.txt` 结尾的文件都按 Auto-Proxy 规则来解析。如果你有多个 Auto-Proxy 规则,可以保存为多个文件,例如 `auto-proxy-1.txt`、`auto-proxy-2.txt` 等等。 + + **注意**:当前,会忽略 Auto-Proxy 中的 URL 的匹配规则,仅仅处理域名规则。 + - **Blocked**:添加到 `domain-rules-blocked.txt` 中的域名将被阻止访问。 - - **Direct**:添加到 `domain-rules-direct.txt` 中的域名将绕过代理,直接连接。 + - **Direct**: + - 添加到 `domain-rules-direct.txt` 中的域名将绕过代理,直接连接。 + - 添加到 `ipv4-rules-direct.txt` 中的 IPv4 网络段(CIDR格式)将绕过代理,直接连接。 + - 添加到 `ipv6-rules-direct.txt` 中的 IPv6 网络段(CIDR格式)将绕过代理,直接连接。 - **Proxy**:添加到 `domain-rules-proxy.txt` 中的域名将使用默认代理。 - 将你的域名添加到合适的文件中,每个域名一行。以 `#` 开头的行被视为注释。例如: + 将你的域名或者IP网络段添加到合适的文件中,每个域名一行。以 `#` 开头的行被视为注释。例如: + 文件 domain-rules-direct.txt 中添加的域名将会绕过代理直接连接 ``` # 直连域名 google.com example.org ``` - 你也可以创建自己的自定义规则文件,文件名应遵循 `domain-rules-.txt` 的格式。例如,`domain-rules-companyProxy.txt` 将使该文件中的所有域名使用 `proxy.pac` 中定义的 `companyProxy` 设置。 + 文件 ipv4-rules-direct.txt 中添加的网络段将会绕过代理直接连接 + ``` + # 直连的 IPv4 网络段 + 192.168.0.0/16 + 114.114.114.114/32 # 如果要添加一个特定 IP 地址,请追加 /32 到 IP 地址后面 + ``` + + 你也可以创建自己的自定义规则文件,文件名应遵循 `-rules-.txt` 的格式。例如,`domain-rules-companyProxy.txt` 将使该文件中的所有域名使用 `proxy.pac` 中定义的 `companyProxy` 设置。`ipv4-rules-block.txt` 将不可访问文件中的所有网络段。 + 2. **生成 `proxy.pac` 文件** @@ -38,7 +59,16 @@ 在项目根目录中会自动生成 `proxy.pac` 文件。 -3. **代理配置** +3. **默认的规则来源** + 构建脚本 [`build.sh`](./build.sh) 默认会下载以下文件,但不会覆盖已有的同名文件: + + - `auto-proxy.txt` + - `ipv4-rules-direct.txt` + - `ipv6-rules-direct.txt` + + 如果你不需要 Auto-Proxy 的规则或者 IP 网络段的规则,请创建同名的空文件即可忽略下载。 + +4. **代理配置** 生成的 `proxy.pac` 文件使用以下默认的代理配置(注意默认代理服务器是 `SOCKS5 127.0.0.1:1080`): @@ -54,7 +84,7 @@ 你可以在生成 `proxy.pac` 后修改这些值,或者直接在原始脚本 `proxy.js` 中进行自定义,以便使用不同的默认设置。请根据实际环境和需求调整这些代理设置。 -4. **测试** +5. **测试** 如果安装了 Node.js,可以使用以下命令运行测试以验证配置: diff --git a/auto-proxy.txt.example b/auto-proxy.txt.example new file mode 100644 index 0000000..738dd5e --- /dev/null +++ b/auto-proxy.txt.example @@ -0,0 +1 @@ +@@||example.com \ No newline at end of file diff --git a/build.sh b/build.sh index 41d843c..96c19fd 100755 --- a/build.sh +++ b/build.sh @@ -13,39 +13,44 @@ fi cd "$(dirname "$0")" declare -A files -files[gfwlist.txt]=https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt -files[china.txt]=https://gaoyifan.github.io/china-operator-ip/china.txt -files[china6.txt]=https://gaoyifan.github.io/china-operator-ip/china6.txt +files[auto-proxy.txt]=https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt +files[ipv4-rules-direct.txt]=https://gaoyifan.github.io/china-operator-ip/china.txt +files[ipv6-rules-direct.txt]=https://gaoyifan.github.io/china-operator-ip/china6.txt +declare -a files_to_be_deleted=() function cleanup() { local retval="$?" - if [[ -n "${del_file_on_exit-}" && -f "${del_file_on_exit}" ]]; then - echo "Remove ${del_file_on_exit}" - rm "${del_file_on_exit}" - exit "$retval" - fi + local file + for file in "${files_to_be_deleted[@]}"; do + if [[ -f "${file}" ]]; then + echo "Remove $file" + rm -v "${file}" + fi + done + exit "$retval" } >&2 trap cleanup EXIT ERR SIGINT for f in "${!files[@]}"; do - del_file_on_exit="$f" url="${files[$f]}" if [[ ! -f "$f" ]]; then + files_to_be_deleted=("$f" "${f}.tmp") if command -v wget &>/dev/null; then - wget -O "$f" "$url" + wget -O "${f}.tmp" "$url" elif command -v curl &>/dev/null; then - curl --output "$f" "$url" + curl --output "${f}.tmp" "$url" else echo "Error: please install wget or curl." exit 1 fi + if [[ "$f" = auto-proxy.txt ]]; then + base64 -d <"${f}.tmp" > "$f" + else + mv "${f}.tmp" "$f" + fi fi done - -del_file_on_exit=auto-proxy.txt -[ auto-proxy.txt -nt gfwlist.txt ] || base64 -d auto-proxy.txt - -del_file_on_exit= +files_to_be_deleted=() domain_segments() { local domain="$1" @@ -65,18 +70,6 @@ if [[ "$actual" = "$expected" ]]; then exit 1 fi >&2 -ipv6_to_array_format() { - while IFS= read -r line; do - local ipv6="${line%%/*}" - local prefix="${line##*/}" - - local expanded_ipv6=$(expand_ipv6 "$ipv6") - local full_hex="${expanded_ipv6//:/}" - - echo " [0x${full_hex:0:16}n, 0x${full_hex:16:16}n, ${prefix}], // ${line}" - done -} - expand_ipv6() { local ipv6="$1" local full_ipv6="" @@ -110,9 +103,7 @@ generate_pac() { declare -A domain_rules local file rule domain - for file in *.txt.example; do - [ -f "${file%.example}" ] || cp -v "${file}" "${file%.example}" - done >&2 + for file in domain-rules-*.txt; do rule="${file#domain-rules-}" rule="${rule%.txt}" @@ -123,62 +114,63 @@ generate_pac() { domain_rules["$domain"]="$rule"; done < "$file" done - - local line item rule parent_rule - while read -r line; do - echo "$line" >&2 - rule= - case "$line" in - (/*) - echo "Skip regrex rul: $line" - ;; - (\!*|\[*) - : comment - ;; - (@@\|\|*) - line="${line#@}" - ;& - (@@\|*) - line="${line#@?|}" - line="${line#*://}" - line="${line%%\%2F*}" - domain="${line%%/*}" - rule=direct - echo "==> direct access: $domain" - ;; - (\|\|*) - line="${line#|}" - ;& - (\|*) - line="${line#|}" - line="${line#*://}" - ;& - ([.a-z0-9]*) - line="${line#.}" - line="${line%%\%2F*}" - domain="${line%%/*}" - domain="${domain#*\**.}" - rule=proxy - echo "==> proxy access: $domain" - ;; - (*) - [[ "$line" =~ ^[[:space:]]*$ ]] || - echo "Skip: $line" - ;; - esac >&2 - [[ -n "${rule}" ]] || continue - [[ -z "${domain_rules[$domain]-}" ]] || continue - parent_rule= - for item in $(domain_segments "$domain"); do - if [[ -n "${domain_rules[$item]-}" ]]; then - parent_rule="${domain_rules[$item]}" - break + for file in auto-proxy*.txt; do + local line item rule parent_rule + while read -r line; do + echo "$line" >&2 + rule= + case "$line" in + (/*) + echo "Skip regrex rul: $line" + ;; + (\!*|\[*) + : comment + ;; + (@@\|\|*) + line="${line#@}" + ;& + (@@\|*) + line="${line#@?|}" + line="${line#*://}" + line="${line%%\%2F*}" + domain="${line%%/*}" + rule=direct + echo "==> direct access: $domain" + ;; + (\|\|*) + line="${line#|}" + ;& + (\|*) + line="${line#|}" + line="${line#*://}" + ;& + ([.a-z0-9]*) + line="${line#.}" + line="${line%%\%2F*}" + domain="${line%%/*}" + domain="${domain#*\**.}" + rule=proxy + echo "==> proxy access: $domain" + ;; + (*) + [[ "$line" =~ ^[[:space:]]*$ ]] || + echo "Skip: $line" + ;; + esac >&2 + [[ -n "${rule}" ]] || continue + [[ -z "${domain_rules[$domain]-}" ]] || continue + parent_rule= + for item in $(domain_segments "$domain"); do + if [[ -n "${domain_rules[$item]-}" ]]; then + parent_rule="${domain_rules[$item]}" + break + fi + done + if [[ -z "$parent_rule" ]] || [[ "$parent_rule" != "$rule" ]]; then + domain_rules["$domain"]="$rule" fi - done - if [[ -z "$parent_rule" ]] || [[ "$parent_rule" != "$rule" ]]; then - domain_rules["$domain"]="$rule" - fi - done < <(sed '/URL Keywords/,/^!/d' auto-proxy.txt) + done < <(sed '/URL Keywords/,/^!/d' "$file") + done local domain rule parent_rule declare -a segments @@ -196,30 +188,61 @@ generate_pac() { done sed -n '1,/ begin of ipv4 networks$/p' "$jsfile" - cat china.txt | - while read -r line; do + for file in ipv4-rules-*.txt; do + rule="${file#ipv?-rules-}" + rule="${rule%%.*}" + [[ "$rule" = @(blocked|direct|proxy) ]] || rule="\"$rule\"" + while IFS= read -r line; do + line="${line%%#*}" + line="${line// }" + echo "$rule: $line" >&2 while IFS=/ read ip prefix; do while IFS=. read n1 n2 n3 n4; do - printf " [0x%02x%02x%02x%02x, %s], // %s\n" "${n1:-0}" "${n2:-0}" "${n3:-0}" "${n4:-0}" "$prefix" "$line" + printf " [0x%02x%02x%02x%02x, %s, %s], // %s\n" "${n1:-0}" "${n2:-0}" "${n3:-0}" "${n4:-0}" "$prefix" "$rule" "$line" done <<< "$ip" done <<< "${line}"; - done + done < "$file" + done | sort -n sed -n '/ end of ipv4 networks$/,/ begin of ipv6 networks$/p' "$jsfile" - ipv6_to_array_format &2 + local ipv6="${line%%/*}" + local prefix="${line##*/}" + + local expanded_ipv6=$(expand_ipv6 "$ipv6") + local full_hex="${expanded_ipv6//:/}" + + echo " [0x${full_hex:0:16}n, 0x${full_hex:16:16}n, ${prefix}, ${rule}], // ${line}" + done < "$file" + done | sort sed -n '/ end of ipv6 networks$/,/ begin of proxy rules$/p' "$jsfile" local domain for domain in "${!domain_rules[@]}"; do rule="${domain_rules[$domain]}" [[ "$rule" = @(blocked|direct|proxy) ]] || rule="\"$rule\"" printf " \"%s\": %s,\n" "$domain" "$rule" - done | sort + done | sort -n sed -n '/ end of proxy rules$/,$p' "$jsfile" } -del_file_on_exit=proxy.pac -generate_pac "./proxy.js" > proxy.pac +is_up_to_date=true +files_to_be_deleted=(proxy.pac) +shopt -s nullglob +for file in "$0" *.js *.txt; do + if [ "$file" -nt proxy.pac ]; then + is_up_to_date=false + break; + fi +done +"$is_up_to_date" || generate_pac "./proxy.js" > proxy.pac if command -v node &>/dev/null; then node proxy.pac test fi -del_file_on_exit= +files_to_be_deleted=() diff --git a/ipv4-rules-direct.txt.example b/ipv4-rules-direct.txt.example new file mode 100644 index 0000000..ea855f2 --- /dev/null +++ b/ipv4-rules-direct.txt.example @@ -0,0 +1,4 @@ +10.0.0.0/8 +100.64.0.0/10 +172.16.0.0/32 +192.168.0.0/16 \ No newline at end of file diff --git a/ipv6-rules-direct.txt.example b/ipv6-rules-direct.txt.example new file mode 100644 index 0000000..9cbba47 --- /dev/null +++ b/ipv6-rules-direct.txt.example @@ -0,0 +1 @@ +2001:db8::/32 \ No newline at end of file diff --git a/proxy.js b/proxy.js index b097b67..4a56fe0 100644 --- a/proxy.js +++ b/proxy.js @@ -21,10 +21,6 @@ function isIpAddress(host) { return ipv4Pattern.test(host) || ipv6Pattern.test(host); } -function isInDirectAccessNetwork(ip) { - return !!findMatchingNetwork(ip, directAccessIPv4Networks, directAccessIPv6Networks); -} - function ipToNumber(ip) { const parts = ip.split('.'); return parts.reduce((acc, part) => (acc << 8) + parseInt(part, 10), 0); @@ -65,172 +61,21 @@ function twoNumbersToIpv6(high, low) { return parts.join(':').replace(/(:0{1,4}){2,}/, '::'); } -function findMatchingNetwork(ip, networks4, networks6) { - if (ip.includes('.')) { // IPv4 - const ipNumber = ipToNumber(ip); - for (let [network, prefix] of networks4) { - mask = subnetMaks32[prefix] - if ((ipNumber & mask) === (network & mask)) { - return [network, prefix]; - } - } - } else { // IPv6 - const [ipHigh, ipLow] = ipv6ToTwoNumbers(ip); - for (let [networkHigh, networkLow, mask] of networks6) { - if (mask>64) { - maskHigh = 0xffffffffffffffffn; - maskLow = subnetMaks64[mask-64]; - } else { - maskHigh = subnetMaks64[mask]; - maskLow = 0x0000000000000000n; - } - if (((ipHigh & maskHigh) === (networkHigh & maskHigh)) && - ((ipLow & maskLow) === (networkLow & maskLow))) { - return [networkHigh, networkLow, mask]; - } - } - } - return null; -} - -function printMatchingNetwork(ip, networks4, networks6) { - const matchedNetwork = findMatchingNetwork(ip, networks4, networks6); - if (matchedNetwork) { - if (ip.includes('.')) { // IPv4 - const [network, prefixLength] = matchedNetwork; - return `${numberToIp(network)}/${prefixLength}`; - } else { // IPv6 - const [networkHigh, networkLow, prefixLength] = matchedNetwork; - return `${twoNumbersToIpv6(networkHigh, networkLow)}/${prefixLength}`; - } - } else { - return null; - } -} - -const subnetMaks32 = [ - 0x00000000, // 0 - 0x80000000, // 1 - 0xc0000000, // 2 - 0xe0000000, // 3 - 0xf0000000, // 4 - 0xf8000000, // 5 - 0xfc000000, // 6 - 0xfe000000, // 7 - 0xff000000, // 8 - 0xff800000, // 9 - 0xffc00000, // 10 - 0xffe00000, // 11 - 0xfff00000, // 12 - 0xfff80000, // 13 - 0xfffc0000, // 14 - 0xfffe0000, // 15 - 0xffff0000, // 16 - 0xffff8000, // 17 - 0xffffc000, // 18 - 0xffffe000, // 19 - 0xfffff000, // 20 - 0xfffff800, // 21 - 0xfffffc00, // 22 - 0xfffffe00, // 23 - 0xffffff00, // 24 - 0xffffff80, // 25 - 0xffffffc0, // 26 - 0xffffffe0, // 27 - 0xfffffff0, // 28 - 0xfffffff8, // 29 - 0xfffffffc, // 30 - 0xfffffffe, // 31 - 0xffffffff, // 32 -]; - -const subnetMaks64 = [ - 0x0000000000000000n, // 0 - 0x8000000000000000n, // 1 - 0xc000000000000000n, // 2 - 0xe000000000000000n, // 3 - 0xf000000000000000n, // 4 - 0xf800000000000000n, // 5 - 0xfc00000000000000n, // 6 - 0xfe00000000000000n, // 7 - 0xff00000000000000n, // 8 - 0xff80000000000000n, // 9 - 0xffc0000000000000n, // 10 - 0xffe0000000000000n, // 11 - 0xfff0000000000000n, // 12 - 0xfff8000000000000n, // 13 - 0xfffc000000000000n, // 14 - 0xfffe000000000000n, // 15 - 0xffff000000000000n, // 16 - 0xffff800000000000n, // 17 - 0xffffc00000000000n, // 18 - 0xffffe00000000000n, // 19 - 0xfffff00000000000n, // 20 - 0xfffff80000000000n, // 21 - 0xfffffc0000000000n, // 22 - 0xfffffe0000000000n, // 23 - 0xffffff0000000000n, // 24 - 0xffffff8000000000n, // 25 - 0xffffffc000000000n, // 26 - 0xffffffe000000000n, // 27 - 0xfffffff000000000n, // 28 - 0xfffffff800000000n, // 29 - 0xfffffffc00000000n, // 30 - 0xfffffffe00000000n, // 31 - 0xffffffff00000000n, // 32 - 0xffffffff80000000n, // 33 - 0xffffffffc0000000n, // 34 - 0xffffffffe0000000n, // 35 - 0xfffffffff0000000n, // 36 - 0xfffffffff8000000n, // 37 - 0xfffffffffc000000n, // 38 - 0xfffffffffe000000n, // 39 - 0xffffffffff000000n, // 40 - 0xffffffffff800000n, // 41 - 0xffffffffffc00000n, // 42 - 0xffffffffffe00000n, // 43 - 0xfffffffffff00000n, // 44 - 0xfffffffffff80000n, // 45 - 0xfffffffffffc0000n, // 46 - 0xfffffffffffe0000n, // 47 - 0xffffffffffff0000n, // 48 - 0xffffffffffff8000n, // 49 - 0xffffffffffffc000n, // 50 - 0xffffffffffffe000n, // 51 - 0xfffffffffffff000n, // 52 - 0xfffffffffffff800n, // 53 - 0xfffffffffffffc00n, // 54 - 0xfffffffffffffe00n, // 55 - 0xffffffffffffff00n, // 56 - 0xffffffffffffff80n, // 57 - 0xffffffffffffffc0n, // 58 - 0xffffffffffffffe0n, // 59 - 0xfffffffffffffff0n, // 60 - 0xfffffffffffffff8n, // 61 - 0xfffffffffffffffcn, // 62 - 0xfffffffffffffffen, // 63 - 0xffffffffffffffffn, // 64 -]; - -const directAccessIPv4Networks = [ - [0xC0A80000, 16], // 192.168.0.0/16 - [0x0A000000, 8], // 10.0.0.0/8 - [0xAC100000, 12], // 172.16.0.0/12 - [0x7F000000, 8], // 127.0.0.0/8 (Loopback) - [0xA9FE0000, 16], // 169.254.0.0/16 (Link Local) - [0x64400000, 10], // 100.64.0.0/10 (Carrier-grade NAT) +const ipv4NetworkRules = [ + [0x7F000000, 8 , direct], // 127.0.0.0/8 (Loopback) + [0xA9FE0000, 16, direct], // 169.254.0.0/16 (Link Local) + [0x64400000, 10, direct], // 100.64.0.0/10 (Carrier-grade NAT) // begin of ipv4 networks // end of ipv4 networks ]; -const directAccessIPv6Networks = [ - [0x0000000000000000n, 0x0000000000000000n, 128], // ::/128 (Unspecified Address) - [0x0000000000000000n, 0x0000000000000001n, 128], // ::1/128 (Loopback Address) - [0x20010db800000000n, 0x0000000000000000n, 32], // 2001:db8::/32 (Documentation Address) - [0xfc00000000000000n, 0x0000000000000000n, 7], // fc00::/7 (Unique Local Address) - [0xff00000000000000n, 0x0000000000000000n, 8], // ff00::/8 (Multicast Address) - [0x2001000000000000n, 0x0000000000000000n, 16], // 2001::/16 (Teredo Address) - [0xfe80000000000000n, 0x0000000000000000n, 10], // fe80::/10 (Link-Local Address) +const ipv6NetworkRules = [ + [0x0000000000000000n, 0x0000000000000000n, 128, direct], // ::/128 (Unspecified Address) + [0x0000000000000000n, 0x0000000000000001n, 128, direct], // ::1/128 (Loopback Address) + [0xfc00000000000000n, 0x0000000000000000n, 7 , direct], // fc00::/7 (Unique Local Address) + [0xff00000000000000n, 0x0000000000000000n, 8 , direct], // ff00::/8 (Multicast Address) + [0x2001000000000000n, 0x0000000000000000n, 16 , direct], // 2001::/16 (Teredo Address) + [0xfe80000000000000n, 0x0000000000000000n, 10 , direct], // fe80::/10 (Link-Local Address) // begin of ipv6 networks // end of ipv6 networks ]; @@ -244,10 +89,194 @@ const proxyRules = { // end of proxy rules }; +class IPv4TrieNode { + constructor() { + this.children = [null, null]; // 0 and 1 + this.isEnd = false; + this.network = null; + this.prefix = null; + this.action = direct; + } +} + +class IPv4PrefixTrie { + constructor() { + this.root = new IPv4TrieNode(); + } + + static buildTrieFromData(data) { + const trie = new IPv4PrefixTrie(); + for (const [network, prefix, action] of data) { + let node = trie.root; + node = IPv4PrefixTrie._insertBits(node, network, prefix); + node.isEnd = true; + node.network = network; + node.prefix = prefix; + node.action = action + } + return trie; + } + + static _insertBits(node, value, bits) { + let mask = 0x80000000; + for (let i = 0; i < bits; i++) { + const bitIndex = ((value & mask) !== 0 ? 1 : 0); + mask = mask >>> 1; + + if (!node.children[bitIndex]) { + node.children[bitIndex] = new IPv4TrieNode(); + } + node = node.children[bitIndex]; + } + return node; + } + + search(ip) { + let node = this.root; + let lastMatch = null; + + node = IPv4PrefixTrie._searchBits(node, ip, 32, (matchedNode) => { + if (matchedNode.isEnd) { + lastMatch = matchedNode; + } + }); + + return lastMatch; + } + + static _searchBits(node, value, bits, callback) { + let mask = 0x80000000; + for (let i = 0; i < bits; i++) { + const bitIndex = ((value & mask) !== 0 ? 1 : 0); + mask = mask >>> 1; + + if (!node.children[bitIndex]) { + return null; + } + node = node.children[bitIndex]; + callback(node); + } + return node; + } +} + +class IPv6TrieNode { + constructor() { + this.children = [null, null]; // 0 and 1 + this.isEnd = false; + this.networkHigh = null; + this.networkLow = null; + this.prefix = null; + this.action = direct; + } +} + +class IPv6PrefixTrie { + constructor() { + this.root = new IPv6TrieNode(); + } + + static buildTrieFromData(data) { + const trie = new IPv6PrefixTrie(); + for (const [networkHigh, networkLow, prefix, action] of data) { + let node = trie.root; + node = IPv6PrefixTrie._insertBits(node, networkHigh, Math.min(prefix, 64)); + if (prefix > 64) { + node = IPv6PrefixTrie._insertBits(node, networkLow, prefix - 64); + } + node.isEnd = true; + node.networkHigh = networkHigh; + node.networkLow = networkLow; + node.prefix = prefix; + node.action = action; + } + return trie; + } + + static _insertBits(node, value, bits) { + let mask = 0x8000000000000000n; + for (let i = 0; i < bits; i++) { + const bitIndex = ((value & mask) !== 0n ? 1 : 0); + mask = mask >> 1n; + + if (!node.children[bitIndex]) { + node.children[bitIndex] = new IPv6TrieNode(); + } + node = node.children[bitIndex]; + } + return node; + } + + search(ipHigh, ipLow) { + let node = this.root; + let lastMatch = null; + + node = IPv6PrefixTrie._searchBits(node, ipHigh, 64, (matchedNode) => { + if (matchedNode.isEnd) { + lastMatch = matchedNode; + } + }); + + if (node) { + node = IPv6PrefixTrie._searchBits(node, ipLow, 64, (matchedNode) => { + if (matchedNode.isEnd) { + lastMatch = matchedNode; + } + }); + } + + return lastMatch; + } + + static _searchBits(node, value, bits, callback) { + let mask = 0x8000000000000000n; + for (let i = 0; i < bits; i++) { + const bitIndex = ((value & mask) !== 0n ? 1 : 0); + mask = mask >> 1n; + + if (!node.children[bitIndex]) { + return null; + } + node = node.children[bitIndex]; + callback(node); + } + return node; + } +} + +const ipv4Trie = IPv4PrefixTrie.buildTrieFromData(ipv4NetworkRules); +const ipv6Trie = IPv6PrefixTrie.buildTrieFromData(ipv6NetworkRules); + +function findMatchingNetwork(ip, networks4, networks6) { + if (ip.includes('.')) { // IPv4 + const ipNumber = ipToNumber(ip); + return ipv4Trie.search(ipNumber); + } else { // IPv6 + const [ipHigh, ipLow] = ipv6ToTwoNumbers(ip); + return ipv6Trie.search(ipHigh, ipLow); + } + return null; +} + +function printMatchingNetwork(ip, networks4, networks6) { + const matchedNetwork = findMatchingNetwork(ip, networks4, networks6); + if (matchedNetwork) { + if (ip.includes('.')) { // IPv4 + const trie = matchedNetwork; + return `${numberToIp(trie.network)}/${trie.prefix}`; + } else { // IPv6 + const trie = matchedNetwork; + return `${twoNumbersToIpv6(trie.networkHigh, trie.networkLow)}/${trie.prefix}`; + } + } else { + return null; + } +} function FindProxyForURL(url, host) { if (isIpAddress(host)) { - if(isInDirectAccessNetwork(host)) { - return DIRECT; + const match = findMatchingNetwork(host); + if(match) { + return proxyBehaviors[match.action] || default_behavior; } else { var action = proxyRules[host]; if (action !== undefined) { @@ -274,15 +303,16 @@ function FindProxyForURL(url, host) { } else if(typeof dnsResolve == 'function') { remote_ip = dnsResolve(host); } - if(remote_ip !== undefined && isInDirectAccessNetwork(remote_ip)) { - return DIRECT + if(remote_ip !== undefined) { + const match = findMatchingNetwork(remote_ip); + if (match) return proxyBehaviors[match.action] || default_behavior; } return default_behavior; } if (typeof process !== 'undefined' && process.argv.includes('test')) { function assertNetwork(ip, expected) { - const result = printMatchingNetwork(ip, directAccessIPv4Networks, directAccessIPv6Networks); + const result = printMatchingNetwork(ip, ipv4NetworkRules, ipv6NetworkRules); if (result === expected) { console.log(`OK: Test for ${ip} passed.`); } else { @@ -316,15 +346,13 @@ if (typeof process !== 'undefined' && process.argv.includes('test')) { } function runTests() { - assertNetwork("192.168.1.10", "192.168.0.0/16"); + assertNetwork("127.234.168.10", "127.0.0.0/8"); assertNetwork("1.1.1.1", null); - assertNetwork("172.19.1.1", "172.16.0.0/12"); - assertNetwork("2001:0db8:85a3:0000:0000:8a2e:0370:7334", "2001:db8::/32"); assertNetwork("fe80::f0:c6b3:c766:9b1e", "fe80::/10"); assertVisitHostWithProxy("com.google"); assertVisitHostWithProxy("domains.google"); assertHostWithDefaultAction("www.not-google"); - assertDirectHost("10.3.4.5"); + assertDirectHost("127.3.4.5"); assertDirectHost("114.114.114.114"); assertBlockedHost("www.whitehouse.com"); }