fix: resolve Discord Cloudflare voice ports #55, smart detect proxy sections #56, and UI enhancements
This commit is contained in:
@@ -1361,7 +1361,7 @@ const dictionary = {
|
||||
"Auto (cooperative with SQM/CAKE)": "Авто (кооперация с SQM/CAKE)",
|
||||
"QoS Priority Engine (DSCP)": "Движок приоритета QoS (DSCP)",
|
||||
"Strategy applied successfully and service reloaded!": "Стратегия успешно применена, сервис перезапущен!",
|
||||
"Subnet %s is a Cloudflare Anycast range used by Discord Voice. Only UDP voice traffic (ports 50000-65535, 3478, 19302, 5000-5020) is routed through this section; web traffic (TCP 80/443) goes direct.": "Подсеть %s — диапазон Cloudflare Anycast, используемый Discord Voice. Только голосовой UDP-трафик (порты 50000-65535, 3478, 19302, 5000-5020) направляется через этот раздел; веб-трафик (TCP 80/443) идет напрямую.",
|
||||
"Subnet %s is a Cloudflare Anycast range used by Discord Voice. Only UDP voice traffic (ports 50000-65535, 3478, 19294-19344, 5000-5020) is routed through this section; web traffic (TCP 80/443) goes direct.": "Подсеть %s — диапазон Cloudflare Anycast, используемый Discord Voice. Только голосовой UDP-трафик (порты 50000-65535, 3478, 19294-19344, 5000-5020) направляется через этот раздел; веб-трафик (TCP 80/443) идет напрямую.",
|
||||
"Add URLs or local paths to .lst / .txt lists containing domains and subnets.": "Добавьте URL или локальные пути к спискам .lst / .txt, содержащим домены и подсети.",
|
||||
"Add URLs or local paths to .lst / .txt lists containing domains. IP entries are ignored.": "Добавьте URL или локальные пути к спискам .lst / .txt, содержащим домены. IP-записи игнорируются.",
|
||||
};
|
||||
|
||||
@@ -12644,8 +12644,8 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"call": "Subnet %s is a Cloudflare Anycast range used by Discord Voice. Only UDP voice traffic (ports 50000-65535, 3478, 19302, 5000-5020) is routed through this section; web traffic (TCP 80/443) goes direct.",
|
||||
"key": "Subnet %s is a Cloudflare Anycast range used by Discord Voice. Only UDP voice traffic (ports 50000-65535, 3478, 19302, 5000-5020) is routed through this section; web traffic (TCP 80/443) goes direct.",
|
||||
"call": "Subnet %s is a Cloudflare Anycast range used by Discord Voice. Only UDP voice traffic (ports 50000-65535, 3478, 19294-19344, 5000-5020) is routed through this section; web traffic (TCP 80/443) goes direct.",
|
||||
"key": "Subnet %s is a Cloudflare Anycast range used by Discord Voice. Only UDP voice traffic (ports 50000-65535, 3478, 19294-19344, 5000-5020) is routed through this section; web traffic (TCP 80/443) goes direct.",
|
||||
"places": [
|
||||
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:11896"
|
||||
]
|
||||
|
||||
@@ -7,8 +7,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: TACHYON\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-09-15 12:05+0300\n"
|
||||
"PO-Revision-Date: 2026-09-15 12:05+0300\n"
|
||||
"POT-Creation-Date: 2026-09-15 12:34+0300\n"
|
||||
"PO-Revision-Date: 2026-09-15 12:34+0300\n"
|
||||
"Last-Translator: Dushnilin <dushnilin@gmail.com>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: \n"
|
||||
@@ -7810,7 +7810,7 @@ msgid "Subnet %s belongs to a shared Anycast/CDN range (%s). Third-party website
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:11896
|
||||
msgid "Subnet %s is a Cloudflare Anycast range used by Discord Voice. Only UDP voice traffic (ports 50000-65535, 3478, 19302, 5000-5020) is routed through this section; web traffic (TCP 80/443) goes direct."
|
||||
msgid "Subnet %s is a Cloudflare Anycast range used by Discord Voice. Only UDP voice traffic (ports 50000-65535, 3478, 19294-19344, 5000-5020) is routed through this section; web traffic (TCP 80/443) goes direct."
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:11387
|
||||
|
||||
@@ -7,8 +7,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: TACHYON\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-09-15 15:05+0300\n"
|
||||
"PO-Revision-Date: 2026-09-15 15:05+0300\n"
|
||||
"POT-Creation-Date: 2026-09-15 15:34+0300\n"
|
||||
"PO-Revision-Date: 2026-09-15 15:34+0300\n"
|
||||
"Last-Translator: Dushnilin\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: ru\n"
|
||||
@@ -4868,8 +4868,8 @@ msgstr "STUN (P2P-звонки)"
|
||||
msgid "Subnet %s belongs to a shared Anycast/CDN range (%s). Third-party websites hosted on this CDN will also be routed through this section."
|
||||
msgstr "Подсеть %s входит в публичный Anycast/CDN-диапазон (%s). Сторонние сайты, размещенные на этом CDN, также будут направляться через эту секцию."
|
||||
|
||||
msgid "Subnet %s is a Cloudflare Anycast range used by Discord Voice. Only UDP voice traffic (ports 50000-65535, 3478, 19302, 5000-5020) is routed through this section; web traffic (TCP 80/443) goes direct."
|
||||
msgstr "Подсеть %s — диапазон Cloudflare Anycast, используемый Discord Voice. Только голосовой UDP-трафик (порты 50000-65535, 3478, 19302, 5000-5020) направляется через этот раздел; веб-трафик (TCP 80/443) идет напрямую."
|
||||
msgid "Subnet %s is a Cloudflare Anycast range used by Discord Voice. Only UDP voice traffic (ports 50000-65535, 3478, 19294-19344, 5000-5020) is routed through this section; web traffic (TCP 80/443) goes direct."
|
||||
msgstr "Подсеть %s — диапазон Cloudflare Anycast, используемый Discord Voice. Только голосовой UDP-трафик (порты 50000-65535, 3478, 19294-19344, 5000-5020) направляется через этот раздел; веб-трафик (TCP 80/443) идет напрямую."
|
||||
|
||||
msgid "Subnet %s is a shared Anycast/CDN range (%s). Third-party websites hosted on this CDN will also be routed through this section."
|
||||
msgstr "Подсеть %s является публичным Anycast/CDN-диапазоном (%s). Сторонние сайты, размещенные на этом CDN, также будут направляться через эту секцию."
|
||||
|
||||
@@ -125,7 +125,7 @@ interface ComponentActionButton {
|
||||
|
||||
interface ComponentCard {
|
||||
component: Tachyon.ComponentName;
|
||||
column: 0 | 1;
|
||||
column: 0 | 1 | 2;
|
||||
title: string;
|
||||
version: string;
|
||||
latestVersion?: string;
|
||||
@@ -1545,7 +1545,7 @@ function getComponentCards(): ComponentCard[] {
|
||||
},
|
||||
{
|
||||
component: 'wdtt',
|
||||
column: 1,
|
||||
column: 2,
|
||||
title: 'WDTT',
|
||||
version: systemInfoLoading
|
||||
? _('Loading...')
|
||||
@@ -1560,7 +1560,7 @@ function getComponentCards(): ComponentCard[] {
|
||||
},
|
||||
{
|
||||
component: 'olcrtc',
|
||||
column: 1,
|
||||
column: 2,
|
||||
title: 'OlcRTC',
|
||||
version: systemInfoLoading
|
||||
? _('Loading...')
|
||||
@@ -1575,7 +1575,7 @@ function getComponentCards(): ComponentCard[] {
|
||||
},
|
||||
{
|
||||
component: 'fptn',
|
||||
column: 1,
|
||||
column: 2,
|
||||
title: 'FPTN',
|
||||
version: systemInfoLoading
|
||||
? _('Loading...')
|
||||
@@ -1590,7 +1590,7 @@ function getComponentCards(): ComponentCard[] {
|
||||
},
|
||||
{
|
||||
component: 'tailscale',
|
||||
column: 1,
|
||||
column: 2,
|
||||
title: 'Tailscale',
|
||||
version: systemInfoLoading
|
||||
? _('Loading...')
|
||||
@@ -2036,22 +2036,19 @@ function renderUpdatesComponents() {
|
||||
return;
|
||||
}
|
||||
|
||||
const columns = [[], []] as Node[][];
|
||||
const columns: Node[][] = [[], [], []];
|
||||
getComponentCards().forEach((card) => {
|
||||
columns[card.column].push(renderComponentCard(card));
|
||||
columns[card.column]?.push(renderComponentCard(card));
|
||||
});
|
||||
|
||||
return preserveScrollForPage(() => {
|
||||
container.replaceChildren(
|
||||
E(
|
||||
'div',
|
||||
{ class: 'tachyon_updates-page__components-column' },
|
||||
columns[0],
|
||||
),
|
||||
E(
|
||||
'div',
|
||||
{ class: 'tachyon_updates-page__components-column' },
|
||||
columns[1],
|
||||
...columns.map((columnNodes) =>
|
||||
E(
|
||||
'div',
|
||||
{ class: 'tachyon_updates-page__components-column' },
|
||||
columnNodes,
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -24,10 +24,10 @@ export const styles = `
|
||||
|
||||
.tachyon_updates-page__components-column {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex: 1 1 0;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
min-width: max-content;
|
||||
min-width: 280px;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
@@ -172,7 +172,7 @@ export const styles = `
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ export const styles = `
|
||||
|
||||
.tachyon_updates-page__component__variants-buttons {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
|
||||
@@ -22103,7 +22103,7 @@ function getComponentCards() {
|
||||
},
|
||||
{
|
||||
component: "wdtt",
|
||||
column: 1,
|
||||
column: 2,
|
||||
title: "WDTT",
|
||||
version: systemInfoLoading ? _("Loading...") : wdttInstalled ? systemInfo.wdtt_version : _("Not installed"),
|
||||
latestVersion: getLatestVersion("wdtt"),
|
||||
@@ -22114,7 +22114,7 @@ function getComponentCards() {
|
||||
},
|
||||
{
|
||||
component: "olcrtc",
|
||||
column: 1,
|
||||
column: 2,
|
||||
title: "OlcRTC",
|
||||
version: systemInfoLoading ? _("Loading...") : olcrtcInstalled ? systemInfo.olcrtc_version : _("Not installed"),
|
||||
latestVersion: getLatestVersion("olcrtc"),
|
||||
@@ -22125,7 +22125,7 @@ function getComponentCards() {
|
||||
},
|
||||
{
|
||||
component: "fptn",
|
||||
column: 1,
|
||||
column: 2,
|
||||
title: "FPTN",
|
||||
version: systemInfoLoading ? _("Loading...") : fptnInstalled ? systemInfo.fptn_version : _("Not installed"),
|
||||
latestVersion: getLatestVersion("fptn"),
|
||||
@@ -22136,7 +22136,7 @@ function getComponentCards() {
|
||||
},
|
||||
{
|
||||
component: "tailscale",
|
||||
column: 1,
|
||||
column: 2,
|
||||
title: "Tailscale",
|
||||
version: systemInfoLoading ? _("Loading...") : tailscaleInstalled ? systemInfo.tailscale_version : _("Not installed"),
|
||||
latestVersion: getLatestVersion("tailscale"),
|
||||
@@ -22516,21 +22516,18 @@ function renderUpdatesComponents() {
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
const columns = [[], []];
|
||||
const columns = [[], [], []];
|
||||
getComponentCards().forEach((card) => {
|
||||
columns[card.column].push(renderComponentCard(card));
|
||||
columns[card.column]?.push(renderComponentCard(card));
|
||||
});
|
||||
return preserveScrollForPage(() => {
|
||||
container.replaceChildren(
|
||||
E(
|
||||
"div",
|
||||
{ class: "tachyon_updates-page__components-column" },
|
||||
columns[0]
|
||||
),
|
||||
E(
|
||||
"div",
|
||||
{ class: "tachyon_updates-page__components-column" },
|
||||
columns[1]
|
||||
...columns.map(
|
||||
(columnNodes) => E(
|
||||
"div",
|
||||
{ class: "tachyon_updates-page__components-column" },
|
||||
columnNodes
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
@@ -22654,10 +22651,10 @@ var styles6 = `
|
||||
|
||||
.tachyon_updates-page__components-column {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex: 1 1 0;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
min-width: max-content;
|
||||
min-width: 280px;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
@@ -22802,7 +22799,7 @@ var styles6 = `
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
@@ -22821,7 +22818,7 @@ var styles6 = `
|
||||
|
||||
.tachyon_updates-page__component__variants-buttons {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
|
||||
@@ -918,20 +918,59 @@ function loadOutboundNameChoices(section_id) {
|
||||
|
||||
const task = readOutboundMetadataFromSectionCache(targetId)
|
||||
.then((metadata) => {
|
||||
const names = Object.values(plainObject(metadata.names));
|
||||
const namesMap = plainObject(metadata.names);
|
||||
const nameCounts = new Map();
|
||||
Object.values(namesMap).forEach((rawName) => {
|
||||
const n = `${rawName || ""}`.trim();
|
||||
if (n) {
|
||||
nameCounts.set(n, (nameCounts.get(n) || 0) + 1);
|
||||
}
|
||||
});
|
||||
|
||||
const typeMap = outboundTypeByNameCache.get(targetId) || new Map();
|
||||
Object.entries(plainObject(metadata.names)).forEach(([tag, name]) => {
|
||||
const choices = [];
|
||||
const seenLabels = new Map();
|
||||
|
||||
Object.entries(namesMap).forEach(([tag, rawName]) => {
|
||||
const name = `${rawName || ""}`.trim();
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
const proto = metadata.protocols ? metadata.protocols[tag] : "";
|
||||
const trans = metadata.transports ? metadata.transports[tag] : "";
|
||||
const label = formatOutboundTypeLabel(proto, trans);
|
||||
if (label) {
|
||||
typeMap.set(name, label);
|
||||
const typeLabel = formatOutboundTypeLabel(proto, trans);
|
||||
if (typeLabel) {
|
||||
typeMap.set(tag, typeLabel);
|
||||
typeMap.set(name, typeLabel);
|
||||
}
|
||||
|
||||
const isDuplicateName = (nameCounts.get(name) || 0) > 1;
|
||||
let choiceValue = name;
|
||||
let choiceLabel = name;
|
||||
|
||||
if (typeLabel) {
|
||||
choiceLabel = `${name} [${typeLabel}]`;
|
||||
if (isDuplicateName) {
|
||||
choiceValue = choiceLabel;
|
||||
}
|
||||
}
|
||||
|
||||
if (seenLabels.has(choiceLabel)) {
|
||||
const count = seenLabels.get(choiceLabel) + 1;
|
||||
seenLabels.set(choiceLabel, count);
|
||||
choiceLabel = `${choiceLabel} (#${count})`;
|
||||
choiceValue = choiceLabel;
|
||||
} else {
|
||||
seenLabels.set(choiceLabel, 1);
|
||||
}
|
||||
|
||||
if (typeLabel) {
|
||||
typeMap.set(choiceValue, typeLabel);
|
||||
}
|
||||
|
||||
choices.push({ value: choiceValue, label: choiceLabel });
|
||||
});
|
||||
|
||||
outboundTypeByNameCache.set(targetId, typeMap);
|
||||
if (targetId !== "Main") {
|
||||
const mainTypeMap = outboundTypeByNameCache.get("Main") || new Map();
|
||||
@@ -939,10 +978,7 @@ function loadOutboundNameChoices(section_id) {
|
||||
outboundTypeByNameCache.set("Main", mainTypeMap);
|
||||
}
|
||||
|
||||
const choices = names
|
||||
.filter(Boolean)
|
||||
.filter((name, index, values) => values.indexOf(name) === index)
|
||||
.sort((a, b) => `${a}`.localeCompare(`${b}`));
|
||||
choices.sort((a, b) => `${a.label}`.localeCompare(`${b.label}`));
|
||||
|
||||
outboundNameChoicesCache.set(targetId, choices);
|
||||
if (targetId !== "Main") {
|
||||
@@ -1890,18 +1926,33 @@ function currentOutboundNameChoices(section_id, values) {
|
||||
|
||||
const seen = new Set();
|
||||
const result = [];
|
||||
const append = (name) => {
|
||||
const value = `${name || ""}`.trim();
|
||||
const append = (item) => {
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
let value = "";
|
||||
let label = "";
|
||||
if (typeof item === "object" && item.value != null) {
|
||||
value = `${item.value || ""}`.trim();
|
||||
label = `${item.label || item.value || ""}`.trim();
|
||||
} else {
|
||||
value = `${item || ""}`.trim();
|
||||
label = value;
|
||||
}
|
||||
if (!value || seen.has(value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
seen.add(value);
|
||||
const typeStr = getOutboundTypeForName(section_id, value);
|
||||
const label =
|
||||
typeStr && !value.endsWith(`[${typeStr}]`)
|
||||
? `${value} [${typeStr}]`
|
||||
: value;
|
||||
if (
|
||||
typeStr &&
|
||||
!label.includes(`[${typeStr}]`) &&
|
||||
!value.includes(`[${typeStr}]`) &&
|
||||
!/\[[^\]]+\]$/.test(label)
|
||||
) {
|
||||
label = `${label} [${typeStr}]`;
|
||||
}
|
||||
result.push({ value, label });
|
||||
};
|
||||
|
||||
@@ -11894,7 +11945,7 @@ async function performTrace(query) {
|
||||
totalSections: totalSections,
|
||||
sharedCdnWarning: isDiscordCfVoice
|
||||
? _(
|
||||
"Subnet %s is a Cloudflare Anycast range used by Discord Voice. Only UDP voice traffic (ports 50000-65535, 3478, 19302, 5000-5020) is routed through this section; web traffic (TCP 80/443) goes direct.",
|
||||
"Subnet %s is a Cloudflare Anycast range used by Discord Voice. Only UDP voice traffic (ports 50000-65535, 3478, 19294-19344, 5000-5020) is routed through this section; web traffic (TCP 80/443) goes direct.",
|
||||
).format(matchedCidr)
|
||||
: getSharedCdnWarning(matchedCidr),
|
||||
};
|
||||
|
||||
@@ -840,9 +840,17 @@ function createSmartDetectSectionsWidget(section_id) {
|
||||
"proxy",
|
||||
"outbound",
|
||||
"vpn",
|
||||
"zapret",
|
||||
"zapret2",
|
||||
"byedpi",
|
||||
"awg",
|
||||
"warp",
|
||||
"anytls",
|
||||
"snell",
|
||||
"mieru",
|
||||
"sudoku",
|
||||
"masque",
|
||||
"openvpn",
|
||||
"wdtt",
|
||||
"olcrtc",
|
||||
"fptn",
|
||||
];
|
||||
const allSections = (uci.sections(UCI_PACKAGE, "section") || [])
|
||||
.filter(function (s) {
|
||||
|
||||
@@ -7,8 +7,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: TACHYON\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-09-15 15:05+0300\n"
|
||||
"PO-Revision-Date: 2026-09-15 15:05+0300\n"
|
||||
"POT-Creation-Date: 2026-09-15 15:34+0300\n"
|
||||
"PO-Revision-Date: 2026-09-15 15:34+0300\n"
|
||||
"Last-Translator: Dushnilin\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: ru\n"
|
||||
@@ -4868,8 +4868,8 @@ msgstr "STUN (P2P-звонки)"
|
||||
msgid "Subnet %s belongs to a shared Anycast/CDN range (%s). Third-party websites hosted on this CDN will also be routed through this section."
|
||||
msgstr "Подсеть %s входит в публичный Anycast/CDN-диапазон (%s). Сторонние сайты, размещенные на этом CDN, также будут направляться через эту секцию."
|
||||
|
||||
msgid "Subnet %s is a Cloudflare Anycast range used by Discord Voice. Only UDP voice traffic (ports 50000-65535, 3478, 19302, 5000-5020) is routed through this section; web traffic (TCP 80/443) goes direct."
|
||||
msgstr "Подсеть %s — диапазон Cloudflare Anycast, используемый Discord Voice. Только голосовой UDP-трафик (порты 50000-65535, 3478, 19302, 5000-5020) направляется через этот раздел; веб-трафик (TCP 80/443) идет напрямую."
|
||||
msgid "Subnet %s is a Cloudflare Anycast range used by Discord Voice. Only UDP voice traffic (ports 50000-65535, 3478, 19294-19344, 5000-5020) is routed through this section; web traffic (TCP 80/443) goes direct."
|
||||
msgstr "Подсеть %s — диапазон Cloudflare Anycast, используемый Discord Voice. Только голосовой UDP-трафик (порты 50000-65535, 3478, 19294-19344, 5000-5020) направляется через этот раздел; веб-трафик (TCP 80/443) идет напрямую."
|
||||
|
||||
msgid "Subnet %s is a shared Anycast/CDN range (%s). Third-party websites hosted on this CDN will also be routed through this section."
|
||||
msgstr "Подсеть %s является публичным Anycast/CDN-диапазоном (%s). Сторонние сайты, размещенные на этом CDN, также будут направляться через эту секцию."
|
||||
|
||||
@@ -7,8 +7,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: TACHYON\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-09-15 12:05+0300\n"
|
||||
"PO-Revision-Date: 2026-09-15 12:05+0300\n"
|
||||
"POT-Creation-Date: 2026-09-15 12:34+0300\n"
|
||||
"PO-Revision-Date: 2026-09-15 12:34+0300\n"
|
||||
"Last-Translator: Dushnilin <dushnilin@gmail.com>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: \n"
|
||||
@@ -7810,7 +7810,7 @@ msgid "Subnet %s belongs to a shared Anycast/CDN range (%s). Third-party website
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:11896
|
||||
msgid "Subnet %s is a Cloudflare Anycast range used by Discord Voice. Only UDP voice traffic (ports 50000-65535, 3478, 19302, 5000-5020) is routed through this section; web traffic (TCP 80/443) goes direct."
|
||||
msgid "Subnet %s is a Cloudflare Anycast range used by Discord Voice. Only UDP voice traffic (ports 50000-65535, 3478, 19294-19344, 5000-5020) is routed through this section; web traffic (TCP 80/443) goes direct."
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:11387
|
||||
|
||||
@@ -55,9 +55,9 @@ config settings 'settings'
|
||||
option game_console_optimizer '0'
|
||||
list game_console_ips ''
|
||||
# smart_detect: auto-detects blocked domains from logs and adds them to the first
|
||||
# section in smart_detect_sections that works via proxy.
|
||||
# list smart_detect_sections 'zapret-section'
|
||||
# remote proxy section in smart_detect_sections that works via proxy.
|
||||
# list smart_detect_sections 'main-proxy'
|
||||
# list smart_detect_sections 'backup-proxy'
|
||||
# Ordered: first section where the domain works via proxy wins.
|
||||
|
||||
# ─── AI Watchdog settings ─────────────────────────────────────────────
|
||||
|
||||
@@ -329,7 +329,8 @@ function is_remote_proxy_action(action) {
|
||||
return is_connections_action(action) ||
|
||||
action == "awg" || action == "warp" ||
|
||||
action == "anytls" || action == "snell" || action == "mieru" || action == "sudoku" ||
|
||||
action == "masque" || action == "openvpn";
|
||||
action == "masque" || action == "openvpn" ||
|
||||
action == "wdtt" || action == "olcrtc" || action == "fptn";
|
||||
}
|
||||
|
||||
function is_wdtt_action(action) {
|
||||
|
||||
@@ -272,9 +272,9 @@ function is_cloudflare_shared_cidr(value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const DISCORD_VOICE_PORTS_NFT = "5000-5020, 3478, 19302, 50000-65535";
|
||||
const DISCORD_VOICE_PORT_RANGES = [ "5000:5020", "3478:3478", "19302:19302", "50000:65535" ];
|
||||
const DEFAULT_DISCORD_VOICE_SUBNETS = [ "162.158.0.0/15", "172.64.0.0/13", "2606:4700::/32" ];
|
||||
const DISCORD_VOICE_PORTS_NFT = "5000-5020, 3478, 19294-19344, 50000-65535";
|
||||
const DISCORD_VOICE_PORT_RANGES = [ "5000:5020", "3478:3478", "19294:19344", "50000:65535" ];
|
||||
const DEFAULT_DISCORD_VOICE_SUBNETS = [ "104.16.0.0/12", "162.158.0.0/15", "172.64.0.0/13", "2606:4700::/32" ];
|
||||
const DISCORD_DEDICATED_SUBNETS = [ "162.159.128.0/21" ];
|
||||
|
||||
return {
|
||||
|
||||
@@ -2046,7 +2046,7 @@ function nft_community_subnet_lines(path, service, filter_mode) {
|
||||
if (data == null) {
|
||||
if (as_string(service) == "discord") {
|
||||
if (filter_mode == "only_cloudflare")
|
||||
return core_ip.DEFAULT_DISCORD_VOICE_SUBNETS || [ "162.158.0.0/15", "172.64.0.0/13", "2606:4700::/32" ];
|
||||
return core_ip.DEFAULT_DISCORD_VOICE_SUBNETS || [ "104.16.0.0/12", "162.158.0.0/15", "172.64.0.0/13", "2606:4700::/32" ];
|
||||
if (filter_mode == "exclude_cloudflare" || filter_mode == null)
|
||||
return core_ip.DISCORD_DEDICATED_SUBNETS || [ "162.159.128.0/21" ];
|
||||
}
|
||||
@@ -2073,7 +2073,7 @@ function nft_community_subnet_lines(path, service, filter_mode) {
|
||||
|
||||
if (as_string(service) == "discord") {
|
||||
if (filter_mode == "only_cloudflare" && length(result) == 0)
|
||||
return core_ip.DEFAULT_DISCORD_VOICE_SUBNETS || [ "162.158.0.0/15", "172.64.0.0/13", "2606:4700::/32" ];
|
||||
return core_ip.DEFAULT_DISCORD_VOICE_SUBNETS || [ "104.16.0.0/12", "162.158.0.0/15", "172.64.0.0/13", "2606:4700::/32" ];
|
||||
if ((filter_mode == "exclude_cloudflare" || filter_mode == null) && length(result) == 0)
|
||||
return core_ip.DISCORD_DEDICATED_SUBNETS || [ "162.159.128.0/21" ];
|
||||
}
|
||||
@@ -2098,7 +2098,7 @@ function nft_add_community_subnet_file_to_family_sets(path, table, ipv4_set, ipv
|
||||
if (as_string(service) == "discord" && ip_ports_v4) {
|
||||
let cf = nft_community_subnet_lines(path, service, "only_cloudflare");
|
||||
if (length(cf) > 0) {
|
||||
let voice_ports = core_ip.DISCORD_VOICE_PORTS_NFT || "5000-5020, 3478, 19302, 50000-65535";
|
||||
let voice_ports = core_ip.DISCORD_VOICE_PORTS_NFT || "5000-5020, 3478, 19294-19344, 50000-65535";
|
||||
let cf_ok = nft_add_values_to_family_sets(cf, table, ip_ports_v4, ip_ports_v6, "ip-port-from-ip", voice_ports, chunk_size_text);
|
||||
ok = ok && cf_ok;
|
||||
}
|
||||
|
||||
@@ -230,18 +230,17 @@ function check_auto_resume_pause() {
|
||||
|
||||
// ─── Smart Detect — self-healing routing ─────────────────────────────────────
|
||||
function smart_detect_get_proxy_sections() {
|
||||
let c = uci_core.cursor();
|
||||
if (!c) return [];
|
||||
c.load(CONFIG_NAME);
|
||||
let sections = uci_core.section_objects(CONFIG_NAME, "section");
|
||||
if (!sections || length(sections) == 0) return [];
|
||||
let connections = require("config.connections");
|
||||
let secs = [];
|
||||
c.foreach(CONFIG_NAME, "section", function(s) {
|
||||
if (s.enabled != "1") return;
|
||||
for (let s in sections) {
|
||||
if (s.enabled != "1") continue;
|
||||
let act = as_string(s.action || "");
|
||||
if (act != "bypass" && act != "block" && act != "dns" && act != "") {
|
||||
if (connections.is_remote_proxy_action(act)) {
|
||||
push(secs, s[".name"]);
|
||||
}
|
||||
});
|
||||
}
|
||||
return secs;
|
||||
}
|
||||
|
||||
@@ -1488,12 +1487,19 @@ function smart_detect_process_pending() {
|
||||
// No http/mixed inbound in the generated config: probing is pointless.
|
||||
if (proxy_addr == "127.0.0.1:") return;
|
||||
|
||||
let valid_proxy_map = {};
|
||||
for (let s in sections) valid_proxy_map[s] = true;
|
||||
|
||||
let detect_sections = [];
|
||||
let raw_list = cfg.smart_detect_sections;
|
||||
if (type(raw_list) == "array") {
|
||||
detect_sections = raw_list;
|
||||
for (let item in raw_list) {
|
||||
let name = trim(as_string(item));
|
||||
if (valid_proxy_map[name]) push(detect_sections, name);
|
||||
}
|
||||
} else if (raw_list && trim(as_string(raw_list)) != "") {
|
||||
detect_sections = [ trim(as_string(raw_list)) ];
|
||||
let name = trim(as_string(raw_list));
|
||||
if (valid_proxy_map[name]) detect_sections = [ name ];
|
||||
}
|
||||
if (length(detect_sections) == 0) {
|
||||
detect_sections = sections;
|
||||
@@ -2276,7 +2282,12 @@ else if (mode == "smart-detect-extract-domain") {
|
||||
print(extracted + "\n");
|
||||
exit(0);
|
||||
}
|
||||
else if (mode == "smart-detect-proxy-sections") {
|
||||
let secs = smart_detect_get_proxy_sections();
|
||||
print(join(" ", secs) + "\n");
|
||||
exit(0);
|
||||
}
|
||||
else {
|
||||
warn("Usage: service/watchdog.uc <start-runtime|stop-runtime|worker|status|ai-heal|ai-status|ai-status-full|smart-detect-extract-domain> ...\n");
|
||||
warn("Usage: service/watchdog.uc <start-runtime|stop-runtime|worker|status|ai-heal|ai-status|ai-status-full|smart-detect-extract-domain|smart-detect-proxy-sections> ...\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -243,9 +243,67 @@ function regex_match_set(tags, names, regexes) {
|
||||
return object_keys_set(runtime_urltest.regex_matching_tag_array(tags, names, regexes));
|
||||
}
|
||||
|
||||
function tag_name_filter_matches(tag, names, name_filter, regex_set) {
|
||||
function format_outbound_type_label(protocol, transport) {
|
||||
let p = lc(as_string(protocol || ""));
|
||||
let norm_proto = "";
|
||||
if (p == "vless") norm_proto = "VLESS";
|
||||
else if (p == "vmess") norm_proto = "VMess";
|
||||
else if (p == "shadowsocks" || p == "ss") norm_proto = "Shadowsocks";
|
||||
else if (p == "trojan") norm_proto = "Trojan";
|
||||
else if (p == "wireguard" || p == "wg") norm_proto = "WireGuard";
|
||||
else if (p == "hysteria2" || p == "hy2") norm_proto = "Hysteria2";
|
||||
else if (p == "hysteria") norm_proto = "Hysteria";
|
||||
else if (p == "tuic") norm_proto = "TUIC";
|
||||
else if (p == "socks" || p == "socks5") norm_proto = "SOCKS5";
|
||||
else if (p == "http") norm_proto = "HTTP";
|
||||
else if (p == "direct") norm_proto = "Direct";
|
||||
else if (p == "block") norm_proto = "Block";
|
||||
else if (p != "") norm_proto = uc(substr(p, 0, 1)) + substr(p, 1);
|
||||
|
||||
let t = lc(as_string(transport || ""));
|
||||
let norm_trans = "";
|
||||
if (t == "xhttp") norm_trans = "XHTTP";
|
||||
else if (t == "ws" || t == "websocket") norm_trans = "WS";
|
||||
else if (t == "grpc") norm_trans = "gRPC";
|
||||
else if (t == "http" || t == "h2") norm_trans = "HTTP";
|
||||
else if (t == "tcp" || t == "raw") norm_trans = "TCP";
|
||||
else if (t == "quic") norm_trans = "QUIC";
|
||||
else if (t == "upgrade" || t == "httpupgrade") norm_trans = "HTTPUpgrade";
|
||||
else if (t != "") norm_trans = uc(t);
|
||||
|
||||
if (norm_proto == "" && norm_trans == "")
|
||||
return "";
|
||||
if (norm_proto == "")
|
||||
return norm_trans;
|
||||
if (norm_trans == "")
|
||||
return norm_proto;
|
||||
if (index(uc(norm_proto), uc(norm_trans)) >= 0)
|
||||
return norm_proto;
|
||||
if (norm_proto == "WireGuard" || norm_proto == "Hysteria" || norm_proto == "Hysteria2" || norm_proto == "TUIC")
|
||||
return norm_proto;
|
||||
return norm_proto + " (" + norm_trans + ")";
|
||||
}
|
||||
|
||||
function tag_display_name_with_type(tag, names, metadata) {
|
||||
let name = tag_display_name(tag, names);
|
||||
return array_contains(name_filter, name) || array_contains(name_filter, tag) || regex_set[tag];
|
||||
let proto = object_or_empty(object_or_empty(metadata).protocols)[tag];
|
||||
let trans = object_or_empty(object_or_empty(metadata).transports)[tag];
|
||||
let label = format_outbound_type_label(proto, trans);
|
||||
return label != "" ? (name + " [" + label + "]") : name;
|
||||
}
|
||||
|
||||
function tag_name_filter_matches(tag, names, name_filter, regex_set, metadata, name_with_type_indexed) {
|
||||
let name = tag_display_name(tag, names);
|
||||
if (array_contains(name_filter, name) || array_contains(name_filter, tag) || regex_set[tag])
|
||||
return true;
|
||||
if (name_with_type_indexed != null && array_contains(name_filter, name_with_type_indexed))
|
||||
return true;
|
||||
if (name_with_type_indexed == null && type(metadata) == "object") {
|
||||
let name_with_type = tag_display_name_with_type(tag, names, metadata);
|
||||
if (name_with_type != name && array_contains(name_filter, name_with_type))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function tag_country_filter_matches(tag, countries, country_filter) {
|
||||
@@ -305,8 +363,14 @@ function urltest_matching_candidate_outbounds(urltest_candidate_tags, names, cou
|
||||
let additional_set = object_keys_set(additional_matches);
|
||||
let result = [];
|
||||
|
||||
let seen_type_counts = {};
|
||||
for (let tag in array_or_empty(urltest_candidate_tags)) {
|
||||
let base_matches = tag_name_filter_matches(tag, names, name_filter, regex_set) ||
|
||||
let name_with_type = tag_display_name_with_type(tag, names, metadata);
|
||||
seen_type_counts[name_with_type] = (seen_type_counts[name_with_type] || 0) + 1;
|
||||
let count = seen_type_counts[name_with_type];
|
||||
let name_with_type_indexed = count > 1 ? (name_with_type + " (#" + count + ")") : name_with_type;
|
||||
|
||||
let base_matches = tag_name_filter_matches(tag, names, name_filter, regex_set, metadata, name_with_type_indexed) ||
|
||||
tag_country_filter_matches(tag, countries, country_filter);
|
||||
let matches = additional_set[tag] || base_matches;
|
||||
if (proxy_parameters_enabled && proxy_parameters_operator == "or") {
|
||||
@@ -1513,7 +1577,7 @@ function load_community_subnet_cidrs(community, filter_mode) {
|
||||
|
||||
if (service == "discord") {
|
||||
if (filter_mode == "only_cloudflare" && length(cidrs) == 0)
|
||||
return core_ip.DEFAULT_DISCORD_VOICE_SUBNETS || [ "162.158.0.0/15", "172.64.0.0/13", "2606:4700::/32" ];
|
||||
return core_ip.DEFAULT_DISCORD_VOICE_SUBNETS || [ "104.16.0.0/12", "162.158.0.0/15", "172.64.0.0/13", "2606:4700::/32" ];
|
||||
if ((filter_mode == "exclude_cloudflare" || filter_mode == null) && length(cidrs) == 0)
|
||||
return core_ip.DISCORD_DEDICATED_SUBNETS || [ "162.159.128.0/21" ];
|
||||
}
|
||||
@@ -1642,7 +1706,7 @@ function add_combined_route_for_section(config, section) {
|
||||
let voice_rule = create_section_route_rule();
|
||||
voice_rule.network = "udp";
|
||||
voice_rule.ip_cidr = discord_cf_subnets;
|
||||
voice_rule.port_range = core_ip.DISCORD_VOICE_PORT_RANGES || [ "5000:5020", "3478:3478", "19302:19302", "50000:65535" ];
|
||||
voice_rule.port_range = core_ip.DISCORD_VOICE_PORT_RANGES || [ "5000:5020", "3478:3478", "19294:19344", "50000:65535" ];
|
||||
push_section_route_rule(config, voice_rule, target.outbound);
|
||||
}
|
||||
|
||||
|
||||
@@ -131,6 +131,14 @@ for (let r in config.route.rules) {
|
||||
warn("Voice UDP rule missing port_range restriction!\n");
|
||||
exit(3);
|
||||
}
|
||||
let has_cf_voice_ports = false;
|
||||
for (let p in r.port_range) {
|
||||
if (p == "19294:19344") has_cf_voice_ports = true;
|
||||
}
|
||||
if (!has_cf_voice_ports) {
|
||||
warn("Voice UDP rule missing 19294:19344 port range!\n");
|
||||
exit(6);
|
||||
}
|
||||
found_voice_udp = true;
|
||||
}
|
||||
}
|
||||
@@ -160,10 +168,18 @@ rm -f /tmp/sing-box/rulesets/community-subnets-discord.lst /tmp/sing-box/ruleset
|
||||
ucode -L "$LIB_DIR" -e '
|
||||
let generator_routes = require("singbox.generator_routes");
|
||||
let fallback_cf = generator_routes.load_community_subnet_cidrs("discord", "only_cloudflare");
|
||||
if (!fallback_cf || length(fallback_cf) < 3) {
|
||||
if (!fallback_cf || length(fallback_cf) != 4) {
|
||||
warn("Fallback voice subnets failed: " + length(fallback_cf) + "\n");
|
||||
exit(1);
|
||||
}
|
||||
let has_104 = false;
|
||||
for (let c in fallback_cf) {
|
||||
if (c == "104.16.0.0/12") has_104 = true;
|
||||
}
|
||||
if (!has_104) {
|
||||
warn("Fallback voice subnets missing 104.16.0.0/12\n");
|
||||
exit(2);
|
||||
}
|
||||
' || fail "singbox.generator_routes fallback voice subnets failed"
|
||||
|
||||
# 5. Test core.ip against discord file lines
|
||||
|
||||
@@ -278,4 +278,104 @@ if (selector.default != "proxy-urltest-ut_auto-out")
|
||||
fail("selector default must be the urltest group");
|
||||
' "$WORK_DIR/issue89-config.json" || fail "issue89 cascade exclusion regression"
|
||||
|
||||
mkdir -p "$WORK_DIR/sub_test/subscriptions"
|
||||
cat >"$WORK_DIR/sub_test/subscriptions/proxy-subscription-1.json" <<'JSON'
|
||||
{
|
||||
"outbounds": [
|
||||
{ "type": "vless", "tag": "dk1", "remark": "🇩🇰 Denmark ⚡", "server": "127.0.0.1", "server_port": 1101, "uuid": "00000000-0000-4000-8000-000000000001", "transport": { "type": "tcp" } },
|
||||
{ "type": "vless", "tag": "dk2", "remark": "🇩🇰 Denmark ⚡", "server": "127.0.0.1", "server_port": 1102, "uuid": "00000000-0000-4000-8000-000000000002", "transport": { "type": "xhttp" } },
|
||||
{ "type": "vless", "tag": "dk3", "remark": "🇩🇰 Denmark ⚡", "server": "127.0.0.1", "server_port": 1103, "uuid": "00000000-0000-4000-8000-000000000003", "transport": { "type": "tcp" } }
|
||||
]
|
||||
}
|
||||
JSON
|
||||
printf '%s\n' 'https://dup.example/sub' >"$WORK_DIR/sub_test/subscriptions/proxy-subscription-1.url"
|
||||
: >"$WORK_DIR/sub_test/subscriptions/proxy-subscription-1.user_agent"
|
||||
|
||||
cat >"$WORK_DIR/duplicate-names.json" <<'JSON'
|
||||
{
|
||||
"settings": {
|
||||
".name": "settings",
|
||||
".type": "settings"
|
||||
},
|
||||
"section": [
|
||||
{
|
||||
".name": "proxy",
|
||||
".type": "section",
|
||||
"enabled": "1",
|
||||
"action": "connection",
|
||||
"subscription_urls": [ "https://dup.example/sub" ]
|
||||
}
|
||||
],
|
||||
"urltest": [
|
||||
{
|
||||
".name": "ut_xhttp_only",
|
||||
".type": "urltest",
|
||||
"section": "proxy",
|
||||
"name": "XHTTP only",
|
||||
"filter_mode": "include",
|
||||
"include_outbounds": [ "🇩🇰 Denmark ⚡ [VLESS (XHTTP)]" ]
|
||||
},
|
||||
{
|
||||
".name": "ut_tcp_only",
|
||||
".type": "urltest",
|
||||
"section": "proxy",
|
||||
"name": "TCP only",
|
||||
"filter_mode": "include",
|
||||
"include_outbounds": [ "🇩🇰 Denmark ⚡ [VLESS (TCP)]" ]
|
||||
},
|
||||
{
|
||||
".name": "ut_tcp_second",
|
||||
".type": "urltest",
|
||||
"section": "proxy",
|
||||
"name": "TCP #2 only",
|
||||
"filter_mode": "include",
|
||||
"include_outbounds": [ "🇩🇰 Denmark ⚡ [VLESS (TCP)] (#2)" ]
|
||||
},
|
||||
{
|
||||
".name": "ut_both_name",
|
||||
".type": "urltest",
|
||||
"section": "proxy",
|
||||
"name": "Both by plain name",
|
||||
"filter_mode": "include",
|
||||
"include_outbounds": [ "🇩🇰 Denmark ⚡" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
JSON
|
||||
|
||||
TMP_SUBSCRIPTION_FOLDER="$WORK_DIR/sub_test/subscriptions" \
|
||||
generate_config "$WORK_DIR/duplicate-names.json" "$WORK_DIR/duplicate-names-config.json"
|
||||
|
||||
ucode -e '
|
||||
let fs = require("fs");
|
||||
function fail(msg) { die(msg + "\n"); }
|
||||
function outbound_by_tag(cfg, tag) {
|
||||
for (let o in cfg.outbounds || [])
|
||||
if (o && o.tag == tag) return o;
|
||||
return null;
|
||||
}
|
||||
function assert_array(val, exp, label) {
|
||||
val = val || [];
|
||||
if (length(val) != length(exp)) fail(label + " len mismatch: " + sprintf("%J", val) + " vs " + sprintf("%J", exp));
|
||||
for (let i = 0; i < length(exp); i++)
|
||||
if (val[i] != exp[i]) fail(label + " mismatch: " + sprintf("%J", val));
|
||||
}
|
||||
let cfg = json(fs.readfile(ARGV[0]));
|
||||
let ut_xhttp = outbound_by_tag(cfg, "proxy-urltest-ut_xhttp_only-out");
|
||||
let ut_tcp = outbound_by_tag(cfg, "proxy-urltest-ut_tcp_only-out");
|
||||
let ut_tcp2 = outbound_by_tag(cfg, "proxy-urltest-ut_tcp_second-out");
|
||||
let ut_both = outbound_by_tag(cfg, "proxy-urltest-ut_both_name-out");
|
||||
|
||||
if (!ut_xhttp) fail("missing ut_xhttp");
|
||||
if (!ut_tcp) fail("missing ut_tcp");
|
||||
if (!ut_tcp2) fail("missing ut_tcp2");
|
||||
if (!ut_both) fail("missing ut_both");
|
||||
|
||||
assert_array(ut_xhttp.outbounds, [ "dk2" ], "XHTTP only must select only the XHTTP node");
|
||||
assert_array(ut_tcp.outbounds, [ "dk1" ], "TCP only must select only the TCP node");
|
||||
assert_array(ut_tcp2.outbounds, [ "dk3" ], "TCP #2 only must select only the second TCP node");
|
||||
assert_array(ut_both.outbounds, [ "dk1", "dk2", "dk3" ], "Plain name must select all three nodes");
|
||||
' "$WORK_DIR/duplicate-names-config.json" || fail "duplicate outbound name disambiguation failed"
|
||||
|
||||
printf 'dashboard server filter checks passed\n'
|
||||
|
||||
|
||||
@@ -88,4 +88,44 @@ do
|
||||
done
|
||||
rm -f "$SENTINEL"
|
||||
|
||||
# --- smart_detect_get_proxy_sections filtering ---
|
||||
# Issue #56: zapret, zapret2, byedpi, bypass, block, dns must NEVER be returned as candidate proxy sections.
|
||||
# Only enabled remote proxy sections (connection, awg, warp, etc.) should be returned.
|
||||
TMP_DIR="$(mktemp -d /tmp/tachyon_smart_detect_test.XXXXXX)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
UCI_STATE_TEST="$TMP_DIR/uci.state"
|
||||
cat > "$UCI_STATE_TEST" << 'EOF'
|
||||
tachyon.sec_bypass=section
|
||||
tachyon.sec_bypass.enabled=1
|
||||
tachyon.sec_bypass.action=bypass
|
||||
tachyon.sec_zapret=section
|
||||
tachyon.sec_zapret.enabled=1
|
||||
tachyon.sec_zapret.action=zapret
|
||||
tachyon.sec_zapret2=section
|
||||
tachyon.sec_zapret2.enabled=1
|
||||
tachyon.sec_zapret2.action=zapret2
|
||||
tachyon.sec_byedpi=section
|
||||
tachyon.sec_byedpi.enabled=1
|
||||
tachyon.sec_byedpi.action=byedpi
|
||||
tachyon.sec_block=section
|
||||
tachyon.sec_block.enabled=1
|
||||
tachyon.sec_block.action=block
|
||||
tachyon.sec_dns=section
|
||||
tachyon.sec_dns.enabled=1
|
||||
tachyon.sec_dns.action=dns
|
||||
tachyon.sec_proxy=section
|
||||
tachyon.sec_proxy.enabled=1
|
||||
tachyon.sec_proxy.action=connection
|
||||
tachyon.sec_awg=section
|
||||
tachyon.sec_awg.enabled=1
|
||||
tachyon.sec_awg.action=awg
|
||||
tachyon.sec_disabled=section
|
||||
tachyon.sec_disabled.enabled=0
|
||||
tachyon.sec_disabled.action=connection
|
||||
EOF
|
||||
|
||||
proxy_secs="$(UCI_STATE="$UCI_STATE_TEST" ucode "$WATCHDOG_UC" smart-detect-proxy-sections 2>/dev/null || true)"
|
||||
[ "$proxy_secs" = "sec_proxy sec_awg" ] || fail "smart_detect_get_proxy_sections: expected 'sec_proxy sec_awg', got '$proxy_secs'"
|
||||
|
||||
printf 'smart_detect_domain_extraction checks passed\n'
|
||||
|
||||
Reference in New Issue
Block a user