feat(subscription): добавлена поддержка пользовательских заголовков устройства и генератор HWID

This commit is contained in:
Dushnilin
2026-08-18 14:40:25 +03:00
parent bc10fff538
commit a161ae4845
6 changed files with 121 additions and 0 deletions
@@ -258,6 +258,13 @@ function hydrateConfigSections(configSections: Tachyon.ConfigSection[]) {
user_agent: item.user_agent,
auto_hwid: item.auto_hwid,
hwid: item.hwid,
custom_device_headers: item.custom_device_headers,
device_os: item.device_os,
ver_os: item.ver_os,
device_model: item.device_model,
device_locale: item.device_locale,
app_version: item.app_version,
accept_language: item.accept_language,
show_dashboard_metadata: item.show_dashboard_metadata,
prefix_nodes: item.prefix_nodes,
node_prefix: item.node_prefix,
+7
View File
@@ -381,6 +381,13 @@ export namespace Tachyon {
user_agent?: string;
auto_hwid?: '0' | '1';
hwid?: string;
custom_device_headers?: '0' | '1';
device_os?: string;
ver_os?: string;
device_model?: string;
device_locale?: string;
app_version?: string;
accept_language?: string;
show_dashboard_metadata?: '0' | '1';
prefix_nodes?: '0' | '1';
node_prefix?: string;
@@ -3968,6 +3968,13 @@ function hydrateConfigSections(configSections) {
user_agent: item.user_agent,
auto_hwid: item.auto_hwid,
hwid: item.hwid,
custom_device_headers: item.custom_device_headers,
device_os: item.device_os,
ver_os: item.ver_os,
device_model: item.device_model,
device_locale: item.device_locale,
app_version: item.app_version,
accept_language: item.accept_language,
show_dashboard_metadata: item.show_dashboard_metadata,
prefix_nodes: item.prefix_nodes,
node_prefix: item.node_prefix,
+7
View File
@@ -102,6 +102,13 @@ config settings 'settings'
# option auto_user_agent '0'
# option user_agent 'CustomAgent/1.0'
# option auto_hwid '1'
# option custom_device_headers '0'
# #option device_os 'iOS'
# #option ver_os '26.4'
# #option device_model 'iPhone 17 Pro Max'
# #option device_locale 'ru'
# #option app_version '4.6.0'
# #option accept_language 'ru-RU,ru;q=0.9'
# option subscription_update_enabled '1'
# option subscription_update_interval '1h'
# option download_via_proxy_enabled '0'
+50
View File
@@ -0,0 +1,50 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
SECTION_JS="$ROOT_DIR/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js"
CACHE_UC="$ROOT_DIR/tachyon/files/usr/lib/subscription/cache.uc"
for key in custom_device_headers device_os ver_os device_model device_locale app_version accept_language; do
grep -Fq "\"$key\"" "$SECTION_JS" || {
printf 'FAIL: missing subscription header field %s\n' "$key" >&2
exit 1
}
done
for header in X-Device-OS X-Ver-OS X-Device-Model X-Device-Locale X-App-Version Accept-Language; do
grep -Fq "$header" "$CACHE_UC" || {
printf 'FAIL: missing request header %s\n' "$header" >&2
exit 1
}
done
kernel_version_line="$(grep -n '^function get_kernel_version' "$CACHE_UC" | cut -d: -f1)"
device_headers_line="$(grep -n '^function device_request_headers' "$CACHE_UC" | cut -d: -f1)"
if [ "$kernel_version_line" -ge "$device_headers_line" ]; then
printf 'FAIL: get_kernel_version must be declared before device_request_headers\n' >&2
exit 1
fi
source="$(sed -n '/^function generateHwid16(/,/^}/p' "$SECTION_JS")"
HWID_SOURCE="$source" node <<'NODE'
global.window = {
crypto: {
getRandomValues(bytes) {
for (let i = 0; i < bytes.length; i += 1) bytes[i] = i + 1;
return bytes;
},
},
};
const generate = Function(`${process.env.HWID_SOURCE}; return generateHwid16`)();
const hwid = generate();
if (hwid !== '0102030405060708') {
throw new Error(`unexpected generated HWID: ${hwid}`);
}
if (!/^[0-9a-f]{16}$/.test(hwid)) {
throw new Error(`generated HWID is not 16 lowercase hex characters: ${hwid}`);
}
NODE
printf 'LuCI subscription header checks passed\n'
+43
View File
@@ -365,6 +365,49 @@ if grep -Fq "left-hand side is not a function" "$WORK_DIR/hwid.err"; then
fi
rm -f "$WORK_DIR/runtime-cache/proxy-subscription-1.hwid"
mkdir -p "$WORK_DIR/custom-header-cache"
cat >"$WORK_DIR/cache-custom-headers.json" <<'JSON'
{
"section": [
{
".name": "custom",
"subscription_urls": [
"https://example.com/custom.txt"
],
"subscription_url_settings": "{\"https://example.com/custom.txt\":{\"user_agent\":\"v2rayN\",\"custom_device_headers\":\"1\",\"device_os\":\"iOS\",\"ver_os\":\"26.4\",\"device_model\":\"iPhone 17 Pro Max\",\"device_locale\":\"ru\",\"app_version\":\"4.6.0\",\"accept_language\":\"ru-RU,ru;q=0.9\"}}"
}
]
}
JSON
cat >"$WORK_DIR/custom-header-cache/custom-subscription-1.json" <<'JSON'
{"outbounds":[{"type":"direct","tag":"custom"}]}
JSON
printf '%s' 'https://example.com/custom.txt' >"$WORK_DIR/custom-header-cache/custom-subscription-1.url"
printf '%s' 'v2rayN' >"$WORK_DIR/custom-header-cache/custom-subscription-1.user_agent"
if cache_ucode section-current-usable-cache-fixture \
"$WORK_DIR/cache-custom-headers.json" custom "$WORK_DIR/custom-header-cache" "$WORK_DIR/persistent-cache" "sing-box/default" >/dev/null 2>&1; then
fail "custom device headers should invalidate a cache without their signature"
fi
cat >"$WORK_DIR/custom-header-cache/custom-subscription-1.json" <<'JSON'
{"outbounds":[{"type":"direct","tag":"custom"}]}
JSON
printf '%s' 'https://example.com/custom.txt' >"$WORK_DIR/custom-header-cache/custom-subscription-1.url"
printf '%s' 'v2rayN' >"$WORK_DIR/custom-header-cache/custom-subscription-1.user_agent"
printf '%s' '{ "enabled": "1", "device_os": "iOS", "ver_os": "26.4", "device_model": "iPhone 17 Pro Max", "device_locale": "ru", "app_version": "4.6.0", "accept_language": "ru-RU,ru;q=0.9" }' >"$WORK_DIR/custom-header-cache/custom-subscription-1.device_headers"
cache_ucode section-current-usable-cache-fixture \
"$WORK_DIR/cache-custom-headers.json" custom "$WORK_DIR/custom-header-cache" "$WORK_DIR/persistent-cache" "sing-box/default" >/dev/null ||
fail "matching custom device headers signature should keep the cache current"
sed 's/\\"device_os\\":\\"iOS\\"/\\"device_os\\":\\"Android\\"/' \
"$WORK_DIR/cache-custom-headers.json" >"$WORK_DIR/cache-custom-headers-changed.json"
if cache_ucode section-current-usable-cache-fixture \
"$WORK_DIR/cache-custom-headers-changed.json" custom "$WORK_DIR/custom-header-cache" "$WORK_DIR/persistent-cache" "sing-box/default" >/dev/null 2>&1; then
fail "changed custom device headers should invalidate cache"
fi
printf '%s' 'https://example.com/stale.txt' >"$WORK_DIR/runtime-cache/proxy-subscription-1.url"
if cache_ucode section-current-usable-cache-fixture \
"$WORK_DIR/cache-current.json" proxy "$WORK_DIR/runtime-cache" "$WORK_DIR/persistent-cache" "sing-box/default" >/dev/null 2>&1; then