Quickly search for matching IP network segments, support for any number of proxy configurations.

This commit is contained in:
Chai Feng 2024-10-08 09:38:38 +08:00
parent fef35b21af
commit 7d686975b9
No known key found for this signature in database
GPG Key ID: 2DCD9A24E523FFD2
7 changed files with 364 additions and 280 deletions

7
.gitignore vendored
View File

@ -1,7 +1,4 @@
.DS_Store .DS_Store
china.txt auto-proxy*.txt
china6.txt *-rules-*.txt
gfwlist.txt
auto-proxy.txt
domain-rules-*.txt
proxy.pac proxy.pac

View File

@ -8,25 +8,46 @@
项目包含一些示例配置文件: 项目包含一些示例配置文件:
- `auto-proxy.txt.example`
- `domain-rules-blocked.txt.example` - `domain-rules-blocked.txt.example`
- `domain-rules-direct.txt.example` - `domain-rules-direct.txt.example`
- `domain-rules-proxy.txt.example` - `domain-rules-proxy.txt.example`
- `ipv4-rules-direct.txt.example`
- `ipv6-rules-direct.txt.example`
要使用这些文件,去掉 `.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` 中的域名将被阻止访问。 - **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` 中的域名将使用默认代理。 - **Proxy**:添加到 `domain-rules-proxy.txt` 中的域名将使用默认代理。
将你的域名添加到合适的文件中,每个域名一行。以 `#` 开头的行被视为注释。例如: 将你的域名或者IP网络段添加到合适的文件中,每个域名一行。以 `#` 开头的行被视为注释。例如:
文件 domain-rules-direct.txt 中添加的域名将会绕过代理直接连接
``` ```
# 直连域名 # 直连域名
google.com google.com
example.org example.org
``` ```
你也可以创建自己的自定义规则文件,文件名应遵循 `domain-rules-<rule_name>.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 地址后面
```
你也可以创建自己的自定义规则文件,文件名应遵循 `<domain|ipv4|ipv6>-rules-<rule_name>.txt` 的格式。例如,`domain-rules-companyProxy.txt` 将使该文件中的所有域名使用 `proxy.pac` 中定义的 `companyProxy` 设置。`ipv4-rules-block.txt` 将不可访问文件中的所有网络段。
2. **生成 `proxy.pac` 文件** 2. **生成 `proxy.pac` 文件**
@ -38,7 +59,16 @@
在项目根目录中会自动生成 `proxy.pac` 文件。 在项目根目录中会自动生成 `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` 生成的 `proxy.pac` 文件使用以下默认的代理配置(注意默认代理服务器是 `SOCKS5 127.0.0.1:1080`
@ -54,7 +84,7 @@
你可以在生成 `proxy.pac` 后修改这些值,或者直接在原始脚本 `proxy.js` 中进行自定义,以便使用不同的默认设置。请根据实际环境和需求调整这些代理设置。 你可以在生成 `proxy.pac` 后修改这些值,或者直接在原始脚本 `proxy.js` 中进行自定义,以便使用不同的默认设置。请根据实际环境和需求调整这些代理设置。
4. **测试** 5. **测试**
如果安装了 Node.js可以使用以下命令运行测试以验证配置 如果安装了 Node.js可以使用以下命令运行测试以验证配置

1
auto-proxy.txt.example Normal file
View File

@ -0,0 +1 @@
@@||example.com

105
build.sh
View File

@ -13,39 +13,44 @@ fi
cd "$(dirname "$0")" cd "$(dirname "$0")"
declare -A files declare -A files
files[gfwlist.txt]=https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt files[auto-proxy.txt]=https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt
files[china.txt]=https://gaoyifan.github.io/china-operator-ip/china.txt files[ipv4-rules-direct.txt]=https://gaoyifan.github.io/china-operator-ip/china.txt
files[china6.txt]=https://gaoyifan.github.io/china-operator-ip/china6.txt files[ipv6-rules-direct.txt]=https://gaoyifan.github.io/china-operator-ip/china6.txt
declare -a files_to_be_deleted=()
function cleanup() { function cleanup() {
local retval="$?" local retval="$?"
if [[ -n "${del_file_on_exit-}" && -f "${del_file_on_exit}" ]]; then local file
echo "Remove ${del_file_on_exit}" for file in "${files_to_be_deleted[@]}"; do
rm "${del_file_on_exit}" if [[ -f "${file}" ]]; then
exit "$retval" echo "Remove $file"
rm -v "${file}"
fi fi
done
exit "$retval"
} >&2 } >&2
trap cleanup EXIT ERR SIGINT trap cleanup EXIT ERR SIGINT
for f in "${!files[@]}"; do for f in "${!files[@]}"; do
del_file_on_exit="$f"
url="${files[$f]}" url="${files[$f]}"
if [[ ! -f "$f" ]]; then if [[ ! -f "$f" ]]; then
files_to_be_deleted=("$f" "${f}.tmp")
if command -v wget &>/dev/null; then if command -v wget &>/dev/null; then
wget -O "$f" "$url" wget -O "${f}.tmp" "$url"
elif command -v curl &>/dev/null; then elif command -v curl &>/dev/null; then
curl --output "$f" "$url" curl --output "${f}.tmp" "$url"
else else
echo "Error: please install wget or curl." echo "Error: please install wget or curl."
exit 1 exit 1
fi fi
if [[ "$f" = auto-proxy.txt ]]; then
base64 -d <"${f}.tmp" > "$f"
else
mv "${f}.tmp" "$f"
fi
fi fi
done done
files_to_be_deleted=()
del_file_on_exit=auto-proxy.txt
[ auto-proxy.txt -nt gfwlist.txt ] || base64 -d <gfwlist.txt >auto-proxy.txt
del_file_on_exit=
domain_segments() { domain_segments() {
local domain="$1" local domain="$1"
@ -65,18 +70,6 @@ if [[ "$actual" = "$expected" ]]; then
exit 1 exit 1
fi >&2 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() { expand_ipv6() {
local ipv6="$1" local ipv6="$1"
local full_ipv6="" local full_ipv6=""
@ -110,9 +103,7 @@ generate_pac() {
declare -A domain_rules declare -A domain_rules
local file rule domain 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 for file in domain-rules-*.txt; do
rule="${file#domain-rules-}" rule="${file#domain-rules-}"
rule="${rule%.txt}" rule="${rule%.txt}"
@ -123,7 +114,7 @@ generate_pac() {
domain_rules["$domain"]="$rule"; domain_rules["$domain"]="$rule";
done < "$file" done < "$file"
done done
for file in auto-proxy*.txt; do
local line item rule parent_rule local line item rule parent_rule
while read -r line; do while read -r line; do
echo "$line" >&2 echo "$line" >&2
@ -178,7 +169,8 @@ generate_pac() {
if [[ -z "$parent_rule" ]] || [[ "$parent_rule" != "$rule" ]]; then if [[ -z "$parent_rule" ]] || [[ "$parent_rule" != "$rule" ]]; then
domain_rules["$domain"]="$rule" domain_rules["$domain"]="$rule"
fi fi
done < <(sed '/URL Keywords/,/^!/d' auto-proxy.txt) done < <(sed '/URL Keywords/,/^!/d' "$file")
done
local domain rule parent_rule local domain rule parent_rule
declare -a segments declare -a segments
@ -196,30 +188,61 @@ generate_pac() {
done done
sed -n '1,/ begin of ipv4 networks$/p' "$jsfile" sed -n '1,/ begin of ipv4 networks$/p' "$jsfile"
cat china.txt | for file in ipv4-rules-*.txt; do
while read -r line; 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 ip prefix; do
while IFS=. read n1 n2 n3 n4; 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 <<< "$ip"
done <<< "${line}"; done <<< "${line}";
done done < "$file"
done | sort -n
sed -n '/ end of ipv4 networks$/,/ begin of ipv6 networks$/p' "$jsfile" sed -n '/ end of ipv4 networks$/,/ begin of ipv6 networks$/p' "$jsfile"
ipv6_to_array_format <china6.txt for file in ipv6-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
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" sed -n '/ end of ipv6 networks$/,/ begin of proxy rules$/p' "$jsfile"
local domain local domain
for domain in "${!domain_rules[@]}"; do for domain in "${!domain_rules[@]}"; do
rule="${domain_rules[$domain]}" rule="${domain_rules[$domain]}"
[[ "$rule" = @(blocked|direct|proxy) ]] || rule="\"$rule\"" [[ "$rule" = @(blocked|direct|proxy) ]] || rule="\"$rule\""
printf " \"%s\": %s,\n" "$domain" "$rule" printf " \"%s\": %s,\n" "$domain" "$rule"
done | sort done | sort -n
sed -n '/ end of proxy rules$/,$p' "$jsfile" sed -n '/ end of proxy rules$/,$p' "$jsfile"
} }
del_file_on_exit=proxy.pac is_up_to_date=true
generate_pac "./proxy.js" > proxy.pac 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 if command -v node &>/dev/null; then
node proxy.pac test node proxy.pac test
fi fi
del_file_on_exit= files_to_be_deleted=()

View File

@ -0,0 +1,4 @@
10.0.0.0/8
100.64.0.0/10
172.16.0.0/32
192.168.0.0/16

View File

@ -0,0 +1 @@
2001:db8::/32

378
proxy.js
View File

@ -21,10 +21,6 @@ function isIpAddress(host) {
return ipv4Pattern.test(host) || ipv6Pattern.test(host); return ipv4Pattern.test(host) || ipv6Pattern.test(host);
} }
function isInDirectAccessNetwork(ip) {
return !!findMatchingNetwork(ip, directAccessIPv4Networks, directAccessIPv6Networks);
}
function ipToNumber(ip) { function ipToNumber(ip) {
const parts = ip.split('.'); const parts = ip.split('.');
return parts.reduce((acc, part) => (acc << 8) + parseInt(part, 10), 0); 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,}/, '::'); return parts.join(':').replace(/(:0{1,4}){2,}/, '::');
} }
function findMatchingNetwork(ip, networks4, networks6) { const ipv4NetworkRules = [
if (ip.includes('.')) { // IPv4 [0x7F000000, 8 , direct], // 127.0.0.0/8 (Loopback)
const ipNumber = ipToNumber(ip); [0xA9FE0000, 16, direct], // 169.254.0.0/16 (Link Local)
for (let [network, prefix] of networks4) { [0x64400000, 10, direct], // 100.64.0.0/10 (Carrier-grade NAT)
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)
// begin of ipv4 networks // begin of ipv4 networks
// end of ipv4 networks // end of ipv4 networks
]; ];
const directAccessIPv6Networks = [ const ipv6NetworkRules = [
[0x0000000000000000n, 0x0000000000000000n, 128], // ::/128 (Unspecified Address) [0x0000000000000000n, 0x0000000000000000n, 128, direct], // ::/128 (Unspecified Address)
[0x0000000000000000n, 0x0000000000000001n, 128], // ::1/128 (Loopback Address) [0x0000000000000000n, 0x0000000000000001n, 128, direct], // ::1/128 (Loopback Address)
[0x20010db800000000n, 0x0000000000000000n, 32], // 2001:db8::/32 (Documentation Address) [0xfc00000000000000n, 0x0000000000000000n, 7 , direct], // fc00::/7 (Unique Local Address)
[0xfc00000000000000n, 0x0000000000000000n, 7], // fc00::/7 (Unique Local Address) [0xff00000000000000n, 0x0000000000000000n, 8 , direct], // ff00::/8 (Multicast Address)
[0xff00000000000000n, 0x0000000000000000n, 8], // ff00::/8 (Multicast Address) [0x2001000000000000n, 0x0000000000000000n, 16 , direct], // 2001::/16 (Teredo Address)
[0x2001000000000000n, 0x0000000000000000n, 16], // 2001::/16 (Teredo Address) [0xfe80000000000000n, 0x0000000000000000n, 10 , direct], // fe80::/10 (Link-Local Address)
[0xfe80000000000000n, 0x0000000000000000n, 10], // fe80::/10 (Link-Local Address)
// begin of ipv6 networks // begin of ipv6 networks
// end of ipv6 networks // end of ipv6 networks
]; ];
@ -244,10 +89,194 @@ const proxyRules = {
// end of proxy rules // 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) { function FindProxyForURL(url, host) {
if (isIpAddress(host)) { if (isIpAddress(host)) {
if(isInDirectAccessNetwork(host)) { const match = findMatchingNetwork(host);
return DIRECT; if(match) {
return proxyBehaviors[match.action] || default_behavior;
} else { } else {
var action = proxyRules[host]; var action = proxyRules[host];
if (action !== undefined) { if (action !== undefined) {
@ -274,15 +303,16 @@ function FindProxyForURL(url, host) {
} else if(typeof dnsResolve == 'function') { } else if(typeof dnsResolve == 'function') {
remote_ip = dnsResolve(host); remote_ip = dnsResolve(host);
} }
if(remote_ip !== undefined && isInDirectAccessNetwork(remote_ip)) { if(remote_ip !== undefined) {
return DIRECT const match = findMatchingNetwork(remote_ip);
if (match) return proxyBehaviors[match.action] || default_behavior;
} }
return default_behavior; return default_behavior;
} }
if (typeof process !== 'undefined' && process.argv.includes('test')) { if (typeof process !== 'undefined' && process.argv.includes('test')) {
function assertNetwork(ip, expected) { function assertNetwork(ip, expected) {
const result = printMatchingNetwork(ip, directAccessIPv4Networks, directAccessIPv6Networks); const result = printMatchingNetwork(ip, ipv4NetworkRules, ipv6NetworkRules);
if (result === expected) { if (result === expected) {
console.log(`OK: Test for ${ip} passed.`); console.log(`OK: Test for ${ip} passed.`);
} else { } else {
@ -316,15 +346,13 @@ if (typeof process !== 'undefined' && process.argv.includes('test')) {
} }
function runTests() { 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("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"); assertNetwork("fe80::f0:c6b3:c766:9b1e", "fe80::/10");
assertVisitHostWithProxy("com.google"); assertVisitHostWithProxy("com.google");
assertVisitHostWithProxy("domains.google"); assertVisitHostWithProxy("domains.google");
assertHostWithDefaultAction("www.not-google"); assertHostWithDefaultAction("www.not-google");
assertDirectHost("10.3.4.5"); assertDirectHost("127.3.4.5");
assertDirectHost("114.114.114.114"); assertDirectHost("114.114.114.114");
assertBlockedHost("www.whitehouse.com"); assertBlockedHost("www.whitehouse.com");
} }