Simplify connection source settings and cascade
This commit is contained in:
+315
-381
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -7,8 +7,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PODKOP PLUS\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-07-09 02:41+0300\n"
|
||||
"PO-Revision-Date: 2026-07-09 02:41+0300\n"
|
||||
"POT-Creation-Date: 2026-07-10 04:26+0300\n"
|
||||
"PO-Revision-Date: 2026-07-10 04:26+0300\n"
|
||||
"Last-Translator: ushan0v\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: ru\n"
|
||||
@@ -113,9 +113,6 @@ msgstr "ALPN"
|
||||
msgid "Alter ID"
|
||||
msgstr "Alter ID"
|
||||
|
||||
msgid "Applicable only for SOCKS and Shadowsocks links"
|
||||
msgstr "Применимо только для ссылок SOCKS и Shadowsocks"
|
||||
|
||||
msgid "Apply section rules only to the specified local IP addresses"
|
||||
msgstr "Применять правила секции только к указанным локальным IP-адресам"
|
||||
|
||||
@@ -305,9 +302,6 @@ msgstr "Подключаться через"
|
||||
msgid "Connection URL"
|
||||
msgstr "URL подключения"
|
||||
|
||||
msgid "Connection URL settings"
|
||||
msgstr "Настройки URL подключения"
|
||||
|
||||
msgid "Connections are unavailable"
|
||||
msgstr "Подключения недоступны"
|
||||
|
||||
@@ -413,30 +407,18 @@ msgstr "DNS через HTTPS (DoH)"
|
||||
msgid "DNS over TLS (DoT)"
|
||||
msgstr "DNS через TLS (DoT)"
|
||||
|
||||
msgid "DNS protocol"
|
||||
msgstr "Протокол DNS"
|
||||
|
||||
msgid "DNS Protocol Type"
|
||||
msgstr "Тип протокола DNS"
|
||||
|
||||
msgid "DNS protocol used by the resolver"
|
||||
msgstr "DNS-протокол, который будет использовать резолвер"
|
||||
|
||||
msgid "DNS Rewrite TTL"
|
||||
msgstr "Перезапись TTL для DNS"
|
||||
|
||||
msgid "DNS server"
|
||||
msgstr "DNS-сервер"
|
||||
|
||||
msgid "DNS Server"
|
||||
msgstr "DNS-сервер"
|
||||
|
||||
msgid "DNS server address cannot be empty"
|
||||
msgstr "Адрес DNS-сервера не может быть пустым"
|
||||
|
||||
msgid "DNS server used by the resolver"
|
||||
msgstr "DNS-сервер, который будет использовать резолвер"
|
||||
|
||||
msgid "Do not panic, everything can be fixed, just..."
|
||||
msgstr "Не паникуйте, всё можно исправить, просто..."
|
||||
|
||||
@@ -455,9 +437,6 @@ msgstr "Не резолвится"
|
||||
msgid "Domain and IP Lists"
|
||||
msgstr "Списки доменов и IPs"
|
||||
|
||||
msgid "Domain Resolver"
|
||||
msgstr "Резолвер доменов"
|
||||
|
||||
msgid "Domains"
|
||||
msgstr "Домены"
|
||||
|
||||
@@ -518,9 +497,6 @@ msgstr "Включить автоматическое обновление уд
|
||||
msgid "Enable autostart"
|
||||
msgstr "Включить автостарт"
|
||||
|
||||
msgid "Enable built-in DNS resolver for domains handled by this section"
|
||||
msgstr "Включить встроенный DNS-резолвер для доменов, обрабатываемых этой секцией"
|
||||
|
||||
msgid "Enable list updates"
|
||||
msgstr "Включить обновление списков"
|
||||
|
||||
@@ -1100,9 +1076,6 @@ msgstr "Имя, которое отображается в списке уров
|
||||
msgid "Network Interface"
|
||||
msgstr "Сетевой интерфейс"
|
||||
|
||||
msgid "Network interface settings"
|
||||
msgstr "Настройки сетевого интерфейса"
|
||||
|
||||
msgid "NFQUEUE range is available"
|
||||
msgstr "Диапазон NFQUEUE свободен"
|
||||
|
||||
@@ -1337,6 +1310,9 @@ msgstr "Порты"
|
||||
msgid "Preferred IP"
|
||||
msgstr "Предпочитаемый IP"
|
||||
|
||||
msgid "Priority"
|
||||
msgstr "Приоритет"
|
||||
|
||||
msgid "Priority details"
|
||||
msgstr "Детали Priority"
|
||||
|
||||
@@ -1581,7 +1557,7 @@ msgid "Server group for automatic lowest-latency selection"
|
||||
msgstr "Группа серверов для автоматического выбора по наименьшей задержке"
|
||||
|
||||
msgid "Server group for priority failover"
|
||||
msgstr "Группа серверов для приоритетного failover"
|
||||
msgstr "Группа автоматически выбирает рабочий узел на основе установленного приоритета."
|
||||
|
||||
msgid "Server information"
|
||||
msgstr "Информация о сервере"
|
||||
@@ -1877,9 +1853,6 @@ msgstr "Тип"
|
||||
msgid "UDP (Unprotected DNS)"
|
||||
msgstr "UDP (Незащищённый DNS)"
|
||||
|
||||
msgid "UDP over TCP"
|
||||
msgstr "UDP через TCP"
|
||||
|
||||
msgid "Unable to validate the ByeDPI strategy through the backend parser."
|
||||
msgstr "Не удалось проверить стратегию ByeDPI через backend-парсер."
|
||||
|
||||
@@ -2000,8 +1973,8 @@ msgstr "Используйте IP-адрес"
|
||||
msgid "Use an IP listen address"
|
||||
msgstr "Используйте IP-адрес для прослушивания"
|
||||
|
||||
msgid "Use another section as an intermediate hop for connecting to this one"
|
||||
msgstr "Использовать другую секцию как промежуточный узел для подключения к этой"
|
||||
msgid "Use another section as an intermediate hop to connect to servers in this section. Does not apply to network interfaces or JSON outbounds."
|
||||
msgstr "Использовать другую секцию как промежуточный узел подключения к серверам этой секции. Не применяется к сетевым интерфейсам и JSON outbound."
|
||||
|
||||
msgid "Use CIDR prefixes like 192.168.1.0/24 or 2001:db8::/32"
|
||||
msgstr "Используйте CIDR-префиксы, например 192.168.1.0/24 или 2001:db8::/32"
|
||||
|
||||
@@ -124,9 +124,7 @@ type PriorityConfig = {
|
||||
};
|
||||
|
||||
type ChildType =
|
||||
| 'connection_url'
|
||||
| 'subscription_url'
|
||||
| 'section_interface'
|
||||
| 'urltest'
|
||||
| 'priority_group'
|
||||
| 'priority_level';
|
||||
@@ -223,9 +221,7 @@ function compactSettingsMap(settings: Record<string, ItemSettings>) {
|
||||
}
|
||||
|
||||
function hydrateConfigSections(configSections: Podkop.ConfigSection[]) {
|
||||
const connectionUrls = childSections(configSections, 'connection_url');
|
||||
const subscriptionUrls = childSections(configSections, 'subscription_url');
|
||||
const interfaces = childSections(configSections, 'section_interface');
|
||||
const urltests = childSections(configSections, 'urltest');
|
||||
const priorityGroups = childSections(configSections, 'priority_group');
|
||||
const priorityLevels = childSections(configSections, 'priority_level');
|
||||
@@ -236,30 +232,10 @@ function hydrateConfigSections(configSections: Podkop.ConfigSection[]) {
|
||||
}
|
||||
|
||||
const next: Podkop.ConfigSection = { ...section };
|
||||
const connectionUrlItems = ownedChildSections(next, connectionUrls);
|
||||
const subscriptionUrlItems = ownedChildSections(next, subscriptionUrls);
|
||||
const interfaceItems = ownedChildSections(next, interfaces);
|
||||
const urltestItems = ownedChildSections(next, urltests);
|
||||
const priorityGroupItems = ownedChildSections(next, priorityGroups);
|
||||
|
||||
if (connectionUrlItems.length) {
|
||||
const settings: Record<string, ItemSettings> = {};
|
||||
next.selector_proxy_links = connectionUrlItems
|
||||
.map((item) => item.url || '')
|
||||
.filter(Boolean);
|
||||
connectionUrlItems.forEach((item) => {
|
||||
if (!item.url) {
|
||||
return;
|
||||
}
|
||||
settings[item.url] = {
|
||||
outbound_detour_enabled: item.outbound_detour_enabled,
|
||||
outbound_detour_section: item.outbound_detour_section,
|
||||
enable_udp_over_tcp: item.enable_udp_over_tcp,
|
||||
};
|
||||
});
|
||||
next.connection_url_settings = compactSettingsMap(settings);
|
||||
}
|
||||
|
||||
if (subscriptionUrlItems.length) {
|
||||
const settings: Record<string, ItemSettings> = {};
|
||||
next.subscription_urls = subscriptionUrlItems
|
||||
@@ -287,24 +263,6 @@ function hydrateConfigSections(configSections: Podkop.ConfigSection[]) {
|
||||
next.subscription_url_settings = compactSettingsMap(settings);
|
||||
}
|
||||
|
||||
if (interfaceItems.length) {
|
||||
const settings: Record<string, ItemSettings> = {};
|
||||
next.interfaces = interfaceItems
|
||||
.map((item) => item.name || '')
|
||||
.filter(Boolean);
|
||||
interfaceItems.forEach((item) => {
|
||||
if (!item.name) {
|
||||
return;
|
||||
}
|
||||
settings[item.name] = {
|
||||
domain_resolver_enabled: item.domain_resolver_enabled,
|
||||
domain_resolver_dns_type: item.domain_resolver_dns_type,
|
||||
domain_resolver_dns_server: item.domain_resolver_dns_server,
|
||||
};
|
||||
});
|
||||
next.interface_settings = compactSettingsMap(settings);
|
||||
}
|
||||
|
||||
if (urltestItems.length) {
|
||||
const settings: Record<string, ItemSettings> = {};
|
||||
next.urltests = urltestItems.map((item) => item['.name']);
|
||||
|
||||
@@ -58,7 +58,6 @@ const PODKOP_MASK_AFTER_TOKEN_SPACE = [
|
||||
'list fully_routed_ips',
|
||||
'option dns_server',
|
||||
'option bootstrap_dns_server',
|
||||
'option domain_resolver_dns_server',
|
||||
'option listen',
|
||||
'option listen_port',
|
||||
'option public_host',
|
||||
@@ -157,11 +156,6 @@ function maskGlobalCheckLine(line: string) {
|
||||
}
|
||||
|
||||
maskedLine = maskOptionPath(maskedLine, "option dns_server '");
|
||||
maskedLine = maskOptionPath(
|
||||
maskedLine,
|
||||
"option domain_resolver_dns_server '",
|
||||
);
|
||||
|
||||
return maskedLine;
|
||||
}
|
||||
|
||||
|
||||
@@ -236,9 +236,7 @@ export namespace Podkop {
|
||||
| 'ruleset'
|
||||
| 'section'
|
||||
| 'server'
|
||||
| 'connection_url'
|
||||
| 'subscription_url'
|
||||
| 'section_interface'
|
||||
| 'urltest'
|
||||
| 'priority_group'
|
||||
| 'priority_level';
|
||||
@@ -262,9 +260,7 @@ export namespace Podkop {
|
||||
subscription_urls?: string[];
|
||||
interfaces?: string[];
|
||||
outbound_jsons?: string[];
|
||||
connection_url_settings?: string;
|
||||
subscription_url_settings?: string;
|
||||
interface_settings?: string;
|
||||
urltests?: string[];
|
||||
urltest_settings?: string;
|
||||
priority_groups?: string[];
|
||||
@@ -323,7 +319,6 @@ export namespace Podkop {
|
||||
regex?: string[];
|
||||
outbound_detour_enabled?: '0' | '1';
|
||||
outbound_detour_section?: string;
|
||||
enable_udp_over_tcp?: '0' | '1';
|
||||
download_via_proxy_enabled?: '0' | '1';
|
||||
download_via_proxy_section?: string;
|
||||
auto_user_agent?: '0' | '1';
|
||||
@@ -334,9 +329,6 @@ export namespace Podkop {
|
||||
include_urltest_groups?: '0' | '1';
|
||||
hide_urltest_group_outbounds?: '0' | '1';
|
||||
hide_detour_outbounds?: '0' | '1';
|
||||
domain_resolver_enabled?: '0' | '1';
|
||||
domain_resolver_dns_type?: string;
|
||||
domain_resolver_dns_server?: string;
|
||||
yacd_secret_key?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -3215,9 +3215,7 @@ function compactSettingsMap(settings) {
|
||||
return Object.keys(settings).length ? JSON.stringify(settings) : void 0;
|
||||
}
|
||||
function hydrateConfigSections(configSections) {
|
||||
const connectionUrls = childSections(configSections, "connection_url");
|
||||
const subscriptionUrls = childSections(configSections, "subscription_url");
|
||||
const interfaces = childSections(configSections, "section_interface");
|
||||
const urltests = childSections(configSections, "urltest");
|
||||
const priorityGroups = childSections(configSections, "priority_group");
|
||||
const priorityLevels = childSections(configSections, "priority_level");
|
||||
@@ -3226,26 +3224,9 @@ function hydrateConfigSections(configSections) {
|
||||
return section;
|
||||
}
|
||||
const next = { ...section };
|
||||
const connectionUrlItems = ownedChildSections(next, connectionUrls);
|
||||
const subscriptionUrlItems = ownedChildSections(next, subscriptionUrls);
|
||||
const interfaceItems = ownedChildSections(next, interfaces);
|
||||
const urltestItems = ownedChildSections(next, urltests);
|
||||
const priorityGroupItems = ownedChildSections(next, priorityGroups);
|
||||
if (connectionUrlItems.length) {
|
||||
const settings = {};
|
||||
next.selector_proxy_links = connectionUrlItems.map((item) => item.url || "").filter(Boolean);
|
||||
connectionUrlItems.forEach((item) => {
|
||||
if (!item.url) {
|
||||
return;
|
||||
}
|
||||
settings[item.url] = {
|
||||
outbound_detour_enabled: item.outbound_detour_enabled,
|
||||
outbound_detour_section: item.outbound_detour_section,
|
||||
enable_udp_over_tcp: item.enable_udp_over_tcp
|
||||
};
|
||||
});
|
||||
next.connection_url_settings = compactSettingsMap(settings);
|
||||
}
|
||||
if (subscriptionUrlItems.length) {
|
||||
const settings = {};
|
||||
next.subscription_urls = subscriptionUrlItems.map((item) => item.url || "").filter(Boolean);
|
||||
@@ -3270,21 +3251,6 @@ function hydrateConfigSections(configSections) {
|
||||
});
|
||||
next.subscription_url_settings = compactSettingsMap(settings);
|
||||
}
|
||||
if (interfaceItems.length) {
|
||||
const settings = {};
|
||||
next.interfaces = interfaceItems.map((item) => item.name || "").filter(Boolean);
|
||||
interfaceItems.forEach((item) => {
|
||||
if (!item.name) {
|
||||
return;
|
||||
}
|
||||
settings[item.name] = {
|
||||
domain_resolver_enabled: item.domain_resolver_enabled,
|
||||
domain_resolver_dns_type: item.domain_resolver_dns_type,
|
||||
domain_resolver_dns_server: item.domain_resolver_dns_server
|
||||
};
|
||||
});
|
||||
next.interface_settings = compactSettingsMap(settings);
|
||||
}
|
||||
if (urltestItems.length) {
|
||||
const settings = {};
|
||||
next.urltests = urltestItems.map((item) => item[".name"]);
|
||||
@@ -9494,7 +9460,6 @@ var PODKOP_MASK_AFTER_TOKEN_SPACE = [
|
||||
"list fully_routed_ips",
|
||||
"option dns_server",
|
||||
"option bootstrap_dns_server",
|
||||
"option domain_resolver_dns_server",
|
||||
"option listen",
|
||||
"option listen_port",
|
||||
"option public_host",
|
||||
@@ -9567,10 +9532,6 @@ function maskGlobalCheckLine(line) {
|
||||
maskedLine = maskAfterTokenSpace(maskedLine, token);
|
||||
}
|
||||
maskedLine = maskOptionPath(maskedLine, "option dns_server '");
|
||||
maskedLine = maskOptionPath(
|
||||
maskedLine,
|
||||
"option domain_resolver_dns_server '"
|
||||
);
|
||||
return maskedLine;
|
||||
}
|
||||
function maskMultilineContinuation(line) {
|
||||
|
||||
@@ -790,6 +790,26 @@ function ensureConnectionsDynamicListStyles() {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pdk-interface-dynlist-label {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
gap: 0.25em;
|
||||
max-width: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.pdk-interface-dynlist-label > img {
|
||||
flex: 0 0 auto;
|
||||
height: 1.35em;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.pdk-interface-dynlist-label > span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pdk-button-add-dynlist > .add-item {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
@@ -827,26 +847,6 @@ function ensureConnectionsDynamicListStyles() {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.pdk-interface-dynlist-label {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
gap: 0.25em;
|
||||
max-width: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.pdk-interface-dynlist-label > img {
|
||||
flex: 0 0 auto;
|
||||
height: 1.35em;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.pdk-interface-dynlist-label > span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
body.modal-overlay-active > #modal_overlay > .modal.cbi-modal > .cbi-map.flash {
|
||||
animation: none !important;
|
||||
}
|
||||
@@ -1377,24 +1377,6 @@ function currentOutboundNameChoices(section_id, values) {
|
||||
return result.sort((a, b) => a.label.localeCompare(b.label));
|
||||
}
|
||||
|
||||
function connectionUrlSupportsUdpOverTcp(value) {
|
||||
const normalized = `${value || ""}`.trim().toLowerCase();
|
||||
return (
|
||||
normalized.startsWith("socks4://") ||
|
||||
normalized.startsWith("socks4a://") ||
|
||||
normalized.startsWith("socks5://") ||
|
||||
normalized.startsWith("ss://") ||
|
||||
normalized.startsWith("shadowsocks://")
|
||||
);
|
||||
}
|
||||
|
||||
function connectionTargetChoices(section_id) {
|
||||
return getOutboundDetourTargetSections(section_id).map((section) => ({
|
||||
value: getUciSectionName(section),
|
||||
label: getUciSectionLabel(section),
|
||||
}));
|
||||
}
|
||||
|
||||
function isDownloadThroughTargetSection(section, currentSectionId) {
|
||||
const sectionName = getUciSectionName(section);
|
||||
const action = (section && section.action) || "";
|
||||
@@ -1435,24 +1417,6 @@ function subscriptionDownloadTargetChoices(section_id) {
|
||||
}));
|
||||
}
|
||||
|
||||
function dnsTypeChoices() {
|
||||
return [
|
||||
{ value: "doh", label: _("DNS over HTTPS (DoH)") },
|
||||
{ value: "dot", label: _("DNS over TLS (DoT)") },
|
||||
{ value: "udp", label: "UDP" },
|
||||
];
|
||||
}
|
||||
|
||||
function dnsServerDatalist(inputId) {
|
||||
return E(
|
||||
"datalist",
|
||||
{ id: inputId },
|
||||
Object.entries(main.DNS_SERVER_OPTIONS).map(([key, label]) =>
|
||||
E("option", { value: key }, _(label)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function isConnectionNetworkInterfaceAllowed(deviceName, device) {
|
||||
if (CONNECTIONS_BLOCKED_INTERFACES.includes(deviceName)) {
|
||||
return false;
|
||||
@@ -1523,7 +1487,29 @@ function refreshNetworkInterfaceOptionValues(option) {
|
||||
});
|
||||
}
|
||||
|
||||
const InterfaceSettingsDynamicList = SettingsDynamicList.extend({
|
||||
const InterfaceUIDynamicList = ui.DynamicList.extend({
|
||||
render() {
|
||||
ensureConnectionsDynamicListStyles();
|
||||
return ui.DynamicList.prototype.render.apply(this, arguments);
|
||||
},
|
||||
|
||||
addItem(dl, value, text, flash) {
|
||||
const itemText =
|
||||
typeof this.options.itemLabel === "function"
|
||||
? this.options.itemLabel(value, text)
|
||||
: text;
|
||||
|
||||
return ui.DynamicList.prototype.addItem.call(
|
||||
this,
|
||||
dl,
|
||||
value,
|
||||
itemText,
|
||||
flash,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const InterfaceDynamicList = form.DynamicList.extend({
|
||||
load(section_id) {
|
||||
return network.getDevices().then(
|
||||
L.bind(function (devices) {
|
||||
@@ -1535,9 +1521,32 @@ const InterfaceSettingsDynamicList = SettingsDynamicList.extend({
|
||||
);
|
||||
},
|
||||
|
||||
validate(section_id, value) {
|
||||
value = childItemInputValue(section_id, value, "section_interface", "name");
|
||||
renderWidget(section_id, _option_index, cfgvalue) {
|
||||
const value = cfgvalue != null ? cfgvalue : this.default;
|
||||
const choices = this.transformChoices();
|
||||
const items = L.toArray(value);
|
||||
const widget = new InterfaceUIDynamicList(items, choices, {
|
||||
id: this.cbid(section_id),
|
||||
sort: this.keylist,
|
||||
allowduplicates: this.allowduplicates,
|
||||
optional: this.optional || this.rmempty,
|
||||
datatype: this.datatype,
|
||||
placeholder: this.placeholder,
|
||||
validate: L.bind(this.validate, this, section_id),
|
||||
disabled: this.readonly != null ? this.readonly : this.map.readonly,
|
||||
itemLabel: (itemValue, itemText) =>
|
||||
renderNetworkInterfaceListItem(
|
||||
this.interfaceDeviceMap
|
||||
? this.interfaceDeviceMap[itemValue]
|
||||
: null,
|
||||
itemValue || itemText,
|
||||
),
|
||||
});
|
||||
|
||||
return widget.render();
|
||||
},
|
||||
|
||||
validate(_section_id, value) {
|
||||
if (!value || value.length === 0) {
|
||||
return true;
|
||||
}
|
||||
@@ -1548,15 +1557,6 @@ const InterfaceSettingsDynamicList = SettingsDynamicList.extend({
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
renderListItemLabel(section_id, value, text) {
|
||||
value = childItemInputValue(section_id, value, "section_interface", "name");
|
||||
|
||||
return renderNetworkInterfaceListItem(
|
||||
this.interfaceDeviceMap ? this.interfaceDeviceMap[value] : null,
|
||||
value || text,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
function urlTestFilterModeChoices() {
|
||||
@@ -1624,22 +1624,6 @@ function optionMapValue(option, section_id, key) {
|
||||
return value == null ? "" : value;
|
||||
}
|
||||
|
||||
function connectionUrlSettingsKeys() {
|
||||
return [
|
||||
"outbound_detour_enabled",
|
||||
"outbound_detour_section",
|
||||
"enable_udp_over_tcp",
|
||||
];
|
||||
}
|
||||
|
||||
function defaultConnectionUrlSettings() {
|
||||
return {
|
||||
outbound_detour_enabled: "0",
|
||||
outbound_detour_section: "",
|
||||
enable_udp_over_tcp: "0",
|
||||
};
|
||||
}
|
||||
|
||||
function subscriptionUrlSettingsKeys() {
|
||||
return [
|
||||
"subscription_update_enabled",
|
||||
@@ -1694,22 +1678,6 @@ function subscriptionUserAgentChoices() {
|
||||
];
|
||||
}
|
||||
|
||||
function interfaceSettingsKeys() {
|
||||
return [
|
||||
"domain_resolver_enabled",
|
||||
"domain_resolver_dns_type",
|
||||
"domain_resolver_dns_server",
|
||||
];
|
||||
}
|
||||
|
||||
function defaultInterfaceSettings() {
|
||||
return {
|
||||
domain_resolver_enabled: "0",
|
||||
domain_resolver_dns_type: "udp",
|
||||
domain_resolver_dns_server: "8.8.8.8",
|
||||
};
|
||||
}
|
||||
|
||||
function urlTestSettingsKeys() {
|
||||
return [
|
||||
"name",
|
||||
@@ -1848,57 +1816,6 @@ function randomPriorityGroupId() {
|
||||
return `pg_${Date.now().toString(16)}`;
|
||||
}
|
||||
|
||||
function addConnectionUrlItemOptions(itemSection, options = {}) {
|
||||
const parentSectionForItem =
|
||||
typeof options.parentSectionId === "function"
|
||||
? options.parentSectionId
|
||||
: parentSectionIdForItem;
|
||||
|
||||
let o = itemSection.option(
|
||||
form.Flag,
|
||||
"outbound_detour_enabled",
|
||||
_("Cascade connection"),
|
||||
_("Use another section as an intermediate hop for connecting to this one"),
|
||||
);
|
||||
o.default = "0";
|
||||
o.rmempty = false;
|
||||
|
||||
o = itemSection.option(
|
||||
form.ListValue,
|
||||
"outbound_detour_section",
|
||||
_("Connect through"),
|
||||
_("Select a transit section"),
|
||||
);
|
||||
o.depends("outbound_detour_enabled", "1");
|
||||
o.load = function (itemId) {
|
||||
const sectionId = parentSectionForItem(itemId);
|
||||
refreshOptionChoices(this, connectionTargetChoices(sectionId));
|
||||
return optionMapValue(this, itemId, "outbound_detour_section") || "";
|
||||
};
|
||||
o.validate = function (itemId, value) {
|
||||
const sectionId = parentSectionForItem(itemId);
|
||||
if (
|
||||
optionMapValue(this, itemId, "outbound_detour_enabled") === "1" &&
|
||||
!value
|
||||
) {
|
||||
return _("Select an intermediate section");
|
||||
}
|
||||
if (value === sectionId) {
|
||||
return _("Current section cannot be used as its own transit section");
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
o = itemSection.option(
|
||||
form.Flag,
|
||||
"enable_udp_over_tcp",
|
||||
_("UDP over TCP"),
|
||||
_("Applicable only for SOCKS and Shadowsocks links"),
|
||||
);
|
||||
o.default = "0";
|
||||
o.rmempty = false;
|
||||
}
|
||||
|
||||
function addSubscriptionUrlItemOptions(itemSection, options = {}) {
|
||||
const parentSectionForItem =
|
||||
typeof options.parentSectionId === "function"
|
||||
@@ -2057,43 +1974,6 @@ function addSubscriptionUrlItemOptions(itemSection, options = {}) {
|
||||
o.rmempty = false;
|
||||
}
|
||||
|
||||
function addInterfaceItemOptions(itemSection) {
|
||||
let o = itemSection.option(
|
||||
form.Flag,
|
||||
"domain_resolver_enabled",
|
||||
_("Domain Resolver"),
|
||||
_("Enable built-in DNS resolver for domains handled by this section"),
|
||||
);
|
||||
o.default = "0";
|
||||
o.rmempty = false;
|
||||
|
||||
o = itemSection.option(
|
||||
form.ListValue,
|
||||
"domain_resolver_dns_type",
|
||||
_("DNS protocol"),
|
||||
_("DNS protocol used by the resolver"),
|
||||
);
|
||||
o.depends("domain_resolver_enabled", "1");
|
||||
dnsTypeChoices().forEach((choice) => o.value(choice.value, choice.label));
|
||||
o.default = "udp";
|
||||
|
||||
o = itemSection.option(
|
||||
form.Value,
|
||||
"domain_resolver_dns_server",
|
||||
_("DNS server"),
|
||||
_("DNS server used by the resolver"),
|
||||
);
|
||||
o.depends("domain_resolver_enabled", "1");
|
||||
o.default = "8.8.8.8";
|
||||
o.validate = function (itemId, value) {
|
||||
if (optionMapValue(this, itemId, "domain_resolver_enabled") !== "1") {
|
||||
return true;
|
||||
}
|
||||
const validation = main.validateDNS(value);
|
||||
return validation.valid ? true : validation.message;
|
||||
};
|
||||
}
|
||||
|
||||
function addUrlTestItemOptions(itemSection, options = {}) {
|
||||
const parentSectionForItem =
|
||||
typeof options.parentSectionId === "function"
|
||||
@@ -2913,17 +2793,6 @@ function ruleSetIncludesSubnets(section_id, value) {
|
||||
);
|
||||
}
|
||||
|
||||
function showConnectionUrlSettingsModal(_section_id, itemValue, option) {
|
||||
return showChildItemSettingsModal(_section_id, itemValue, option, {
|
||||
typeName: "connection_url",
|
||||
valueOption: "url",
|
||||
keys: connectionUrlSettingsKeys(),
|
||||
defaults: defaultConnectionUrlSettings(),
|
||||
addOptions: addConnectionUrlItemOptions,
|
||||
title: () => _("Connection URL settings"),
|
||||
});
|
||||
}
|
||||
|
||||
function showSubscriptionUrlSettingsModal(_section_id, itemValue, option) {
|
||||
return showChildItemSettingsModal(_section_id, itemValue, option, {
|
||||
typeName: "subscription_url",
|
||||
@@ -3187,17 +3056,6 @@ function validateOutboundJsonItemsBeforeSave(_section_id, values) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function showInterfaceSettingsModal(_section_id, itemValue, option) {
|
||||
return showChildItemSettingsModal(_section_id, itemValue, option, {
|
||||
typeName: "section_interface",
|
||||
valueOption: "name",
|
||||
keys: interfaceSettingsKeys(),
|
||||
defaults: defaultInterfaceSettings(),
|
||||
addOptions: addInterfaceItemOptions,
|
||||
title: () => _("Network interface settings"),
|
||||
});
|
||||
}
|
||||
|
||||
function showRuleSetSettingsModal(section_id, itemValue, option, widget) {
|
||||
const data = {
|
||||
settings: {
|
||||
@@ -6473,47 +6331,17 @@ function createSectionContent(section) {
|
||||
|
||||
o = section.taboption(
|
||||
"settings",
|
||||
SettingsDynamicList,
|
||||
"connection_url",
|
||||
form.DynamicList,
|
||||
"selector_proxy_links",
|
||||
_("Connection URL"),
|
||||
_(
|
||||
"vless://, vmess://, ss://, trojan://, socks4/5://, http(s)://, hy2/hysteria2:// links",
|
||||
),
|
||||
);
|
||||
o.depends("action", "connection");
|
||||
o.rmempty = true;
|
||||
o.modalonly = true;
|
||||
o.childType = "connection_url";
|
||||
o.childValueOption = "url";
|
||||
o.childDefaults = defaultConnectionUrlSettings();
|
||||
o.renderItemSettingsModal = showConnectionUrlSettingsModal;
|
||||
o.hasItemSettings = function (section_id, value) {
|
||||
const normalized = `${value || ""}`.trim();
|
||||
if (isExistingChildItem(section_id, normalized, "connection_url")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.validate(section_id, normalized) === true;
|
||||
};
|
||||
o.stagedChildSettings = function (section_id, value) {
|
||||
const inputValue = childItemInputValue(
|
||||
section_id,
|
||||
value,
|
||||
"connection_url",
|
||||
"url",
|
||||
);
|
||||
const store = childPendingSettingsStore(this, section_id);
|
||||
return store[inputValue] ? Object.assign({}, store[inputValue]) : null;
|
||||
};
|
||||
o.clearStagedChildSettings = function (section_id) {
|
||||
if (this.pendingChildSettings) {
|
||||
delete this.pendingChildSettings[section_id];
|
||||
}
|
||||
};
|
||||
o.renderListItemLabel = function (section_id, itemId) {
|
||||
return childItemInputValue(section_id, itemId, "connection_url", "url");
|
||||
};
|
||||
o.validate = function (_section_id, value) {
|
||||
value = childItemInputValue(_section_id, value, "connection_url", "url");
|
||||
if (!value || value.length === 0) {
|
||||
return true;
|
||||
}
|
||||
@@ -6572,8 +6400,8 @@ function createSectionContent(section) {
|
||||
|
||||
o = section.taboption(
|
||||
"settings",
|
||||
InterfaceSettingsDynamicList,
|
||||
"interface",
|
||||
InterfaceDynamicList,
|
||||
"interfaces",
|
||||
_("Network Interface"),
|
||||
_("Select network interface for VPN connection"),
|
||||
);
|
||||
@@ -6581,33 +6409,6 @@ function createSectionContent(section) {
|
||||
o.rmempty = true;
|
||||
o.modalonly = true;
|
||||
o.placeholder = _("Select a network interface");
|
||||
o.childType = "section_interface";
|
||||
o.childValueOption = "name";
|
||||
o.childDefaults = defaultInterfaceSettings();
|
||||
o.renderItemSettingsModal = showInterfaceSettingsModal;
|
||||
o.hasItemSettings = function (section_id, value) {
|
||||
const normalized = `${value || ""}`.trim();
|
||||
if (isExistingChildItem(section_id, normalized, "section_interface")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.validate(section_id, normalized) === true;
|
||||
};
|
||||
o.stagedChildSettings = function (section_id, value) {
|
||||
const inputValue = childItemInputValue(
|
||||
section_id,
|
||||
value,
|
||||
"section_interface",
|
||||
"name",
|
||||
);
|
||||
const store = childPendingSettingsStore(this, section_id);
|
||||
return store[inputValue] ? Object.assign({}, store[inputValue]) : null;
|
||||
};
|
||||
o.clearStagedChildSettings = function (section_id) {
|
||||
if (this.pendingChildSettings) {
|
||||
delete this.pendingChildSettings[section_id];
|
||||
}
|
||||
};
|
||||
|
||||
o = section.taboption(
|
||||
"settings",
|
||||
@@ -6695,7 +6496,7 @@ function createSectionContent(section) {
|
||||
"settings",
|
||||
ButtonAddSettingsDynamicList,
|
||||
"priority_group",
|
||||
"Priority",
|
||||
_("Priority"),
|
||||
_("Server group for priority failover"),
|
||||
);
|
||||
o.depends("action", "connection");
|
||||
@@ -6728,6 +6529,70 @@ function createSectionContent(section) {
|
||||
);
|
||||
};
|
||||
|
||||
o = section.taboption(
|
||||
"settings",
|
||||
form.Flag,
|
||||
"outbound_detour_enabled",
|
||||
_("Cascade connection"),
|
||||
_(
|
||||
"Use another section as an intermediate hop to connect to servers in this section. Does not apply to network interfaces or JSON outbounds.",
|
||||
),
|
||||
);
|
||||
o.default = "0";
|
||||
o.rmempty = false;
|
||||
o.depends("action", "connection");
|
||||
o.modalonly = true;
|
||||
o.write = function (section_id, value) {
|
||||
if (value === "1") {
|
||||
const currentValue =
|
||||
uci.get(UCI_PACKAGE, section_id, "outbound_detour_section") || "";
|
||||
const targetSections = getOutboundDetourTargetSections(section_id);
|
||||
const currentIsValid = targetSections.some(
|
||||
(targetSection) => getUciSectionName(targetSection) === currentValue,
|
||||
);
|
||||
const selectedValue = currentIsValid
|
||||
? currentValue
|
||||
: getDefaultOutboundDetourSection(section_id);
|
||||
|
||||
if (selectedValue) {
|
||||
uci.set(
|
||||
UCI_PACKAGE,
|
||||
section_id,
|
||||
"outbound_detour_section",
|
||||
selectedValue,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return form.Flag.prototype.write.apply(this, arguments);
|
||||
};
|
||||
|
||||
o = section.taboption(
|
||||
"settings",
|
||||
form.ListValue,
|
||||
"outbound_detour_section",
|
||||
_("Connect through"),
|
||||
_("Select a transit section"),
|
||||
);
|
||||
o.rmempty = false;
|
||||
o.depends({ action: "connection", outbound_detour_enabled: "1" });
|
||||
o.modalonly = true;
|
||||
o.load = function (section_id) {
|
||||
refreshOutboundDetourSectionOptionValues(this, section_id);
|
||||
return Promise.resolve(
|
||||
uci.get(UCI_PACKAGE, section_id, "outbound_detour_section") || "",
|
||||
);
|
||||
};
|
||||
o.validate = function (section_id, value) {
|
||||
if (!value) {
|
||||
return _("Select an intermediate section");
|
||||
}
|
||||
if (value === section_id) {
|
||||
return _("Current section cannot be used as its own transit section");
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
o = section.taboption(
|
||||
"settings",
|
||||
form.Flag,
|
||||
|
||||
@@ -7,8 +7,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PODKOP PLUS\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-07-09 02:41+0300\n"
|
||||
"PO-Revision-Date: 2026-07-09 02:41+0300\n"
|
||||
"POT-Creation-Date: 2026-07-10 04:26+0300\n"
|
||||
"PO-Revision-Date: 2026-07-10 04:26+0300\n"
|
||||
"Last-Translator: ushan0v\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: ru\n"
|
||||
@@ -113,9 +113,6 @@ msgstr "ALPN"
|
||||
msgid "Alter ID"
|
||||
msgstr "Alter ID"
|
||||
|
||||
msgid "Applicable only for SOCKS and Shadowsocks links"
|
||||
msgstr "Применимо только для ссылок SOCKS и Shadowsocks"
|
||||
|
||||
msgid "Apply section rules only to the specified local IP addresses"
|
||||
msgstr "Применять правила секции только к указанным локальным IP-адресам"
|
||||
|
||||
@@ -305,9 +302,6 @@ msgstr "Подключаться через"
|
||||
msgid "Connection URL"
|
||||
msgstr "URL подключения"
|
||||
|
||||
msgid "Connection URL settings"
|
||||
msgstr "Настройки URL подключения"
|
||||
|
||||
msgid "Connections are unavailable"
|
||||
msgstr "Подключения недоступны"
|
||||
|
||||
@@ -413,30 +407,18 @@ msgstr "DNS через HTTPS (DoH)"
|
||||
msgid "DNS over TLS (DoT)"
|
||||
msgstr "DNS через TLS (DoT)"
|
||||
|
||||
msgid "DNS protocol"
|
||||
msgstr "Протокол DNS"
|
||||
|
||||
msgid "DNS Protocol Type"
|
||||
msgstr "Тип протокола DNS"
|
||||
|
||||
msgid "DNS protocol used by the resolver"
|
||||
msgstr "DNS-протокол, который будет использовать резолвер"
|
||||
|
||||
msgid "DNS Rewrite TTL"
|
||||
msgstr "Перезапись TTL для DNS"
|
||||
|
||||
msgid "DNS server"
|
||||
msgstr "DNS-сервер"
|
||||
|
||||
msgid "DNS Server"
|
||||
msgstr "DNS-сервер"
|
||||
|
||||
msgid "DNS server address cannot be empty"
|
||||
msgstr "Адрес DNS-сервера не может быть пустым"
|
||||
|
||||
msgid "DNS server used by the resolver"
|
||||
msgstr "DNS-сервер, который будет использовать резолвер"
|
||||
|
||||
msgid "Do not panic, everything can be fixed, just..."
|
||||
msgstr "Не паникуйте, всё можно исправить, просто..."
|
||||
|
||||
@@ -455,9 +437,6 @@ msgstr "Не резолвится"
|
||||
msgid "Domain and IP Lists"
|
||||
msgstr "Списки доменов и IPs"
|
||||
|
||||
msgid "Domain Resolver"
|
||||
msgstr "Резолвер доменов"
|
||||
|
||||
msgid "Domains"
|
||||
msgstr "Домены"
|
||||
|
||||
@@ -518,9 +497,6 @@ msgstr "Включить автоматическое обновление уд
|
||||
msgid "Enable autostart"
|
||||
msgstr "Включить автостарт"
|
||||
|
||||
msgid "Enable built-in DNS resolver for domains handled by this section"
|
||||
msgstr "Включить встроенный DNS-резолвер для доменов, обрабатываемых этой секцией"
|
||||
|
||||
msgid "Enable list updates"
|
||||
msgstr "Включить обновление списков"
|
||||
|
||||
@@ -1100,9 +1076,6 @@ msgstr "Имя, которое отображается в списке уров
|
||||
msgid "Network Interface"
|
||||
msgstr "Сетевой интерфейс"
|
||||
|
||||
msgid "Network interface settings"
|
||||
msgstr "Настройки сетевого интерфейса"
|
||||
|
||||
msgid "NFQUEUE range is available"
|
||||
msgstr "Диапазон NFQUEUE свободен"
|
||||
|
||||
@@ -1337,6 +1310,9 @@ msgstr "Порты"
|
||||
msgid "Preferred IP"
|
||||
msgstr "Предпочитаемый IP"
|
||||
|
||||
msgid "Priority"
|
||||
msgstr "Приоритет"
|
||||
|
||||
msgid "Priority details"
|
||||
msgstr "Детали Priority"
|
||||
|
||||
@@ -1581,7 +1557,7 @@ msgid "Server group for automatic lowest-latency selection"
|
||||
msgstr "Группа серверов для автоматического выбора по наименьшей задержке"
|
||||
|
||||
msgid "Server group for priority failover"
|
||||
msgstr "Группа серверов для приоритетного failover"
|
||||
msgstr "Группа автоматически выбирает рабочий узел на основе установленного приоритета."
|
||||
|
||||
msgid "Server information"
|
||||
msgstr "Информация о сервере"
|
||||
@@ -1877,9 +1853,6 @@ msgstr "Тип"
|
||||
msgid "UDP (Unprotected DNS)"
|
||||
msgstr "UDP (Незащищённый DNS)"
|
||||
|
||||
msgid "UDP over TCP"
|
||||
msgstr "UDP через TCP"
|
||||
|
||||
msgid "Unable to validate the ByeDPI strategy through the backend parser."
|
||||
msgstr "Не удалось проверить стратегию ByeDPI через backend-парсер."
|
||||
|
||||
@@ -2000,8 +1973,8 @@ msgstr "Используйте IP-адрес"
|
||||
msgid "Use an IP listen address"
|
||||
msgstr "Используйте IP-адрес для прослушивания"
|
||||
|
||||
msgid "Use another section as an intermediate hop for connecting to this one"
|
||||
msgstr "Использовать другую секцию как промежуточный узел для подключения к этой"
|
||||
msgid "Use another section as an intermediate hop to connect to servers in this section. Does not apply to network interfaces or JSON outbounds."
|
||||
msgstr "Использовать другую секцию как промежуточный узел подключения к серверам этой секции. Не применяется к сетевым интерфейсам и JSON outbound."
|
||||
|
||||
msgid "Use CIDR prefixes like 192.168.1.0/24 or 2001:db8::/32"
|
||||
msgstr "Используйте CIDR-префиксы, например 192.168.1.0/24 или 2001:db8::/32"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -29,18 +29,16 @@ config settings 'settings'
|
||||
# option label 'Subscription'
|
||||
# option enabled '1'
|
||||
# option action 'connection'
|
||||
# list selector_proxy_links 'vless://example'
|
||||
# list interfaces 'awg0'
|
||||
# list community_lists 'russia_inside'
|
||||
# list rule_set 'https://example.com/rules.srs'
|
||||
# list rule_set_with_subnets 'https://example.com/rules-with-subnets.json'
|
||||
# option outbound_detour_enabled '0'
|
||||
# #option outbound_detour_section 'main'
|
||||
# option sort_by_latency '0'
|
||||
# #list fully_routed_ips '192.168.1.2'
|
||||
|
||||
#config connection_url
|
||||
# option section 'subscription_example'
|
||||
# option url 'vless://example'
|
||||
# option outbound_detour_enabled '0'
|
||||
# option enable_udp_over_tcp '0'
|
||||
|
||||
#config subscription_url
|
||||
# option section 'subscription_example'
|
||||
# option url 'https://example.com/api/sub'
|
||||
|
||||
@@ -8,9 +8,7 @@ let object_or_empty = common.object_or_empty;
|
||||
|
||||
const CONFIG_NAME = getenv("PODKOP_CONFIG_NAME") || "podkop-plus";
|
||||
const ITEM_TYPES = [
|
||||
"connection_url",
|
||||
"subscription_url",
|
||||
"section_interface",
|
||||
"urltest",
|
||||
"priority_group",
|
||||
"priority_level"
|
||||
@@ -310,7 +308,7 @@ function action(section) {
|
||||
}
|
||||
|
||||
function connection_urls(section) {
|
||||
return child_values(section, "connection_url", "url", "selector_proxy_links");
|
||||
return whitespace_list_value(section, "selector_proxy_links");
|
||||
}
|
||||
|
||||
function subscription_urls(section) {
|
||||
@@ -318,10 +316,6 @@ function subscription_urls(section) {
|
||||
}
|
||||
|
||||
function interfaces(section) {
|
||||
let items = child_values(section, "section_interface", "name", "");
|
||||
if (length(items) > 0)
|
||||
return items;
|
||||
|
||||
let result = list_value(section, "interfaces");
|
||||
if (length(result) == 0) {
|
||||
let value = option(section, "interface", "");
|
||||
@@ -442,21 +436,11 @@ function has_connection_sources(section) {
|
||||
length(outbound_jsons(section)) > 0;
|
||||
}
|
||||
|
||||
function connection_url_settings(section, value) {
|
||||
let child = child_item_by_value(section, "connection_url", "url", value);
|
||||
return child != null ? child : item_settings(section, "connection_url_settings", value);
|
||||
}
|
||||
|
||||
function subscription_url_settings(section, value) {
|
||||
let child = child_item_by_value(section, "subscription_url", "url", value);
|
||||
return child != null ? child : item_settings(section, "subscription_url_settings", value);
|
||||
}
|
||||
|
||||
function interface_settings(section, value) {
|
||||
let child = child_item_by_value(section, "section_interface", "name", value);
|
||||
return child != null ? child : item_settings(section, "interface_settings", value);
|
||||
}
|
||||
|
||||
function urltest_child(section, value) {
|
||||
let child = owned_child_section(section, "urltest", value);
|
||||
if (child != null)
|
||||
@@ -577,54 +561,6 @@ function subscription_download_section(section, value) {
|
||||
return item_option(section, "subscription_url_settings", value, "download_via_proxy_section", "");
|
||||
}
|
||||
|
||||
function connection_detour_enabled(section, value) {
|
||||
let child = child_item_by_value(section, "connection_url", "url", value);
|
||||
if (child != null)
|
||||
return child_bool(child, "outbound_detour_enabled", false);
|
||||
return item_bool(section, "connection_url_settings", value, "outbound_detour_enabled",
|
||||
bool_option(section, "outbound_detour_enabled", false));
|
||||
}
|
||||
|
||||
function connection_detour_section(section, value) {
|
||||
let child = child_item_by_value(section, "connection_url", "url", value);
|
||||
if (child != null)
|
||||
return child_option(child, "outbound_detour_section", "");
|
||||
return item_option(section, "connection_url_settings", value, "outbound_detour_section",
|
||||
option(section, "outbound_detour_section", ""));
|
||||
}
|
||||
|
||||
function connection_udp_over_tcp(section, value) {
|
||||
let child = child_item_by_value(section, "connection_url", "url", value);
|
||||
if (child != null)
|
||||
return child_bool(child, "enable_udp_over_tcp", false);
|
||||
return item_bool(section, "connection_url_settings", value, "enable_udp_over_tcp",
|
||||
bool_option(section, "enable_udp_over_tcp", false));
|
||||
}
|
||||
|
||||
function interface_domain_resolver_enabled(section, value) {
|
||||
let child = child_item_by_value(section, "section_interface", "name", value);
|
||||
if (child != null)
|
||||
return child_bool(child, "domain_resolver_enabled", false);
|
||||
return item_bool(section, "interface_settings", value, "domain_resolver_enabled",
|
||||
bool_option(section, "domain_resolver_enabled", false));
|
||||
}
|
||||
|
||||
function interface_domain_resolver_dns_type(section, value) {
|
||||
let child = child_item_by_value(section, "section_interface", "name", value);
|
||||
if (child != null)
|
||||
return child_option(child, "domain_resolver_dns_type", "udp");
|
||||
return item_option(section, "interface_settings", value, "domain_resolver_dns_type",
|
||||
option(section, "domain_resolver_dns_type", "udp") || "udp");
|
||||
}
|
||||
|
||||
function interface_domain_resolver_dns_server(section, value) {
|
||||
let child = child_item_by_value(section, "section_interface", "name", value);
|
||||
if (child != null)
|
||||
return child_option(child, "domain_resolver_dns_server", "8.8.8.8");
|
||||
return item_option(section, "interface_settings", value, "domain_resolver_dns_server",
|
||||
option(section, "domain_resolver_dns_server", "8.8.8.8") || "8.8.8.8");
|
||||
}
|
||||
|
||||
function urltest_check_interval(section, value) {
|
||||
let child = urltest_child(section, value);
|
||||
if (child != null)
|
||||
@@ -938,9 +874,7 @@ return {
|
||||
rule_sets_value,
|
||||
rule_sets_with_subnets_value,
|
||||
has_connection_sources,
|
||||
connection_url_settings,
|
||||
subscription_url_settings,
|
||||
interface_settings,
|
||||
urltest_settings,
|
||||
subscription_update_enabled,
|
||||
subscription_update_interval,
|
||||
@@ -953,12 +887,6 @@ return {
|
||||
subscription_user_agent,
|
||||
subscription_hwid,
|
||||
subscription_download_section,
|
||||
connection_detour_enabled,
|
||||
connection_detour_section,
|
||||
connection_udp_over_tcp,
|
||||
interface_domain_resolver_enabled,
|
||||
interface_domain_resolver_dns_type,
|
||||
interface_domain_resolver_dns_server,
|
||||
urltest_check_interval,
|
||||
urltest_tolerance,
|
||||
urltest_testing_url,
|
||||
|
||||
@@ -30,9 +30,7 @@ const PODKOP_RUNTIME_CACHE_FORMAT = getenv("PODKOP_RUNTIME_CACHE_FORMAT") || "7"
|
||||
const SERVER_COUNTRY_METHOD_FLAG_EMOJI = "flag_emoji";
|
||||
const SERVER_COUNTRY_METHOD_COUNTRY_IS = "country_is";
|
||||
const CHILD_ITEM_TYPES = [
|
||||
"connection_url",
|
||||
"subscription_url",
|
||||
"section_interface",
|
||||
"urltest"
|
||||
];
|
||||
|
||||
@@ -714,34 +712,6 @@ function migrate_zapret_nfqws_default(ctx, section, constants) {
|
||||
set_option(ctx, section, "nfqws_opt", constants.zapret_default_nfqws_opt);
|
||||
}
|
||||
|
||||
function migrate_connection_url_item_settings(ctx, section) {
|
||||
let values = whitespace_list_values(section, "selector_proxy_links");
|
||||
let udp_over_tcp_enabled = bool_option(section, "enable_udp_over_tcp", false);
|
||||
let detour_enabled = bool_option(section, "outbound_detour_enabled", false);
|
||||
let detour_section = option(section, "outbound_detour_section", "");
|
||||
let index = 1;
|
||||
|
||||
for (let value in values) {
|
||||
value = as_string(value);
|
||||
if (value == "")
|
||||
continue;
|
||||
|
||||
let child = create_child_for_section(ctx, section, "connection_url");
|
||||
set_option(ctx, child, "url", value);
|
||||
if (detour_enabled) {
|
||||
set_option(ctx, child, "outbound_detour_enabled", "1");
|
||||
if (detour_section != "")
|
||||
set_option(ctx, child, "outbound_detour_section", detour_section);
|
||||
}
|
||||
if (udp_over_tcp_enabled)
|
||||
set_option(ctx, child, "enable_udp_over_tcp", "1");
|
||||
index++;
|
||||
}
|
||||
|
||||
delete_option(ctx, section, "selector_proxy_links");
|
||||
delete_option(ctx, section, "connection_url_settings");
|
||||
}
|
||||
|
||||
function migrate_subscription_url_item_settings(ctx, section) {
|
||||
let values = whitespace_list_values(section, "subscription_urls");
|
||||
let seen_values = {};
|
||||
@@ -808,55 +778,51 @@ function migrate_urltest_item_settings(ctx, section, constants) {
|
||||
delete_option(ctx, section, "urltest_settings");
|
||||
}
|
||||
|
||||
function migrate_interface_item_settings(ctx, section) {
|
||||
let values = option_list_values(section, "interfaces");
|
||||
let legacy_interface = option(section, "interface", "");
|
||||
if (legacy_interface != "")
|
||||
push(values, legacy_interface);
|
||||
|
||||
let seen_values = {};
|
||||
let resolver_enabled = bool_option(section, "domain_resolver_enabled", false);
|
||||
let dns_type = option(section, "domain_resolver_dns_type", "");
|
||||
let dns_server = option(section, "domain_resolver_dns_server", "");
|
||||
let index = 1;
|
||||
|
||||
for (let value in values) {
|
||||
value = as_string(value);
|
||||
if (value == "" || seen_values[value])
|
||||
continue;
|
||||
seen_values[value] = true;
|
||||
|
||||
let child = create_child_for_section(ctx, section, "section_interface");
|
||||
set_option(ctx, child, "name", value);
|
||||
if (resolver_enabled) {
|
||||
set_option(ctx, child, "domain_resolver_enabled", "1");
|
||||
set_option(ctx, child, "domain_resolver_dns_type", dns_type != "" ? dns_type : "udp");
|
||||
set_option(ctx, child, "domain_resolver_dns_server", dns_server != "" ? dns_server : "8.8.8.8");
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
delete_option(ctx, section, "interface");
|
||||
delete_option(ctx, section, "interfaces");
|
||||
delete_option(ctx, section, "interface_settings");
|
||||
function migrate_interface_list(ctx, section) {
|
||||
normalize_connections_list(ctx, section, "interface", "interfaces");
|
||||
}
|
||||
|
||||
function migrate_outbound_json_list(ctx, section) {
|
||||
function migrate_legacy_outbound_json_detour(ctx, section, legacy_connection_kind) {
|
||||
if (legacy_connection_kind != "outbound" ||
|
||||
!bool_option(section, "outbound_detour_enabled", false))
|
||||
return;
|
||||
|
||||
let detour_section = option(section, "outbound_detour_section", "");
|
||||
let outbound_json = option(section, "outbound_json", "");
|
||||
if (detour_section == "" || outbound_json == "")
|
||||
return;
|
||||
|
||||
let outbound;
|
||||
try {
|
||||
outbound = json(outbound_json);
|
||||
}
|
||||
catch (e) {
|
||||
return;
|
||||
}
|
||||
if (type(outbound) != "object")
|
||||
return;
|
||||
|
||||
if (as_string(outbound.detour || "") == "")
|
||||
outbound.detour = singbox_constants_module.outbound_tag(detour_section);
|
||||
set_option(ctx, section, "outbound_json", sprintf("%J", outbound));
|
||||
delete_option(ctx, section, "outbound_detour_enabled");
|
||||
delete_option(ctx, section, "outbound_detour_section");
|
||||
}
|
||||
|
||||
function migrate_outbound_json_list(ctx, section, legacy_connection_kind) {
|
||||
migrate_legacy_outbound_json_detour(ctx, section, legacy_connection_kind);
|
||||
normalize_connections_list(ctx, section, "outbound_json", "outbound_jsons");
|
||||
}
|
||||
|
||||
function migrate_connection_section(ctx, section, constants) {
|
||||
migrate_connection_url_item_settings(ctx, section);
|
||||
function migrate_connection_section(ctx, section, constants, legacy_connection_kind) {
|
||||
migrate_subscription_url_item_settings(ctx, section);
|
||||
migrate_urltest_item_settings(ctx, section, constants);
|
||||
migrate_interface_item_settings(ctx, section);
|
||||
migrate_outbound_json_list(ctx, section);
|
||||
migrate_interface_list(ctx, section);
|
||||
migrate_outbound_json_list(ctx, section, legacy_connection_kind);
|
||||
|
||||
delete_option(ctx, section, "subscription_update_enabled");
|
||||
delete_option(ctx, section, "subscription_update_interval");
|
||||
delete_option(ctx, section, "enable_udp_over_tcp");
|
||||
delete_option(ctx, section, "outbound_detour_enabled");
|
||||
delete_option(ctx, section, "outbound_detour_section");
|
||||
delete_option(ctx, section, "domain_resolver_enabled");
|
||||
delete_option(ctx, section, "domain_resolver_dns_type");
|
||||
delete_option(ctx, section, "domain_resolver_dns_server");
|
||||
@@ -1042,7 +1008,7 @@ function migrate_rule(ctx, section, converted_from_rule, constants) {
|
||||
delete_option(ctx, section, "urltest_check_interval_disabled");
|
||||
delete_option(ctx, section, "subscription_update_interval_disabled");
|
||||
}
|
||||
migrate_connection_section(ctx, section, constants);
|
||||
migrate_connection_section(ctx, section, constants, legacy_connection_kind);
|
||||
if (converted_from_rule && subscription_urls != "")
|
||||
delete_subscription_cache(ctx, section_name(section));
|
||||
}
|
||||
|
||||
@@ -425,18 +425,6 @@ function valid_inbound() {
|
||||
return type(value) == "object" && type(value.type) == "string";
|
||||
}
|
||||
|
||||
function outbound_detour_supported() {
|
||||
let value = read_stdin_json();
|
||||
if (type(value) != "object" || type(value.type) != "string")
|
||||
return false;
|
||||
|
||||
let outbound_type = lc(as_string(value.type));
|
||||
return outbound_type != "selector" &&
|
||||
outbound_type != "urltest" &&
|
||||
outbound_type != "block" &&
|
||||
outbound_type != "dns";
|
||||
}
|
||||
|
||||
function bool_flag(value) {
|
||||
value = as_string(value);
|
||||
return value == "1" || value == "true";
|
||||
@@ -452,24 +440,6 @@ function outbound_detour_target_action(action) {
|
||||
return connections.is_connections_action(action);
|
||||
}
|
||||
|
||||
function outbound_detour_supported_json(value) {
|
||||
try {
|
||||
value = json(as_string(value));
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type(value) != "object" || type(value.type) != "string")
|
||||
return false;
|
||||
|
||||
let outbound_type = lc(as_string(value.type));
|
||||
return outbound_type != "selector" &&
|
||||
outbound_type != "urltest" &&
|
||||
outbound_type != "block" &&
|
||||
outbound_type != "dns";
|
||||
}
|
||||
|
||||
function outbound_detour_rows() {
|
||||
let rows = [];
|
||||
|
||||
@@ -484,8 +454,7 @@ function outbound_detour_rows() {
|
||||
enabled: bool_flag(fields[1]),
|
||||
action: as_string(fields[2]),
|
||||
detour_enabled: bool_flag(fields[3]),
|
||||
detour_section: as_string(fields[4]),
|
||||
outbound_json: as_string(fields[5])
|
||||
detour_section: as_string(fields[4])
|
||||
};
|
||||
|
||||
push(rows, row);
|
||||
@@ -576,9 +545,6 @@ function validate_outbound_detours_rows(rows) {
|
||||
fail_outbound_detour("Outbound cascade for rule '" + row.section + "' creates a cycle through '" +
|
||||
row.detour_section + "'. Aborted.");
|
||||
|
||||
if (row.action == "outbound" && !outbound_detour_supported_json(row.outbound_json))
|
||||
fail_outbound_detour("JSON outbound rule '" + row.section +
|
||||
"' cannot use outbound cascade because its sing-box outbound type does not support Dial Fields. Aborted.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -812,7 +778,7 @@ function fixture_get_section(section_name) {
|
||||
if (section_name == "settings" && type(fixture.settings) == "object")
|
||||
return fixture.settings;
|
||||
|
||||
for (let type_name in [ "settings", "server", "section", "connection_url", "subscription_url", "section_interface", "urltest", "priority_group", "priority_level" ]) {
|
||||
for (let type_name in [ "settings", "server", "section", "subscription_url", "urltest", "priority_group", "priority_level" ]) {
|
||||
for (let section in fixture_section_list(type_name)) {
|
||||
if (as_string(section[".name"]) == section_name)
|
||||
return section;
|
||||
@@ -1372,8 +1338,7 @@ function detour_rows_from_sections(sections) {
|
||||
enabled: section_enabled(section),
|
||||
action: rule_action(section),
|
||||
detour_enabled: bool_option(section, "outbound_detour_enabled", false),
|
||||
detour_section: option(section, "outbound_detour_section", ""),
|
||||
outbound_json: option(section, "outbound_json", "")
|
||||
detour_section: option(section, "outbound_detour_section", "")
|
||||
});
|
||||
}
|
||||
return rows;
|
||||
@@ -1415,36 +1380,6 @@ function basic_rows_by_section(rows) {
|
||||
return result;
|
||||
}
|
||||
|
||||
function validate_connection_url_detours(sections) {
|
||||
let by_section = basic_rows_by_section(basic_rows_from_sections(sections));
|
||||
|
||||
for (let section in sections) {
|
||||
section = object_or_empty(section);
|
||||
if (!section_enabled(section))
|
||||
continue;
|
||||
|
||||
let name = section_name(section);
|
||||
for (let entry in connections.connection_urls(section)) {
|
||||
if (!connections.connection_detour_enabled(section, entry))
|
||||
continue;
|
||||
|
||||
let target_name = connections.connection_detour_section(section, entry);
|
||||
if (target_name == "")
|
||||
fail_validation("Connection URL cascade is enabled in rule '" + name + "', but no intermediate rule is selected. Aborted.");
|
||||
if (target_name == name)
|
||||
fail_validation("Connection URL cascade in rule '" + name + "' cannot point to itself. Aborted.");
|
||||
|
||||
let target = by_section[target_name];
|
||||
if (type(target) != "object")
|
||||
fail_validation("Connection URL cascade in rule '" + name + "' references missing rule '" + target_name + "'. Aborted.");
|
||||
if (!target.enabled)
|
||||
fail_validation("Connection URL cascade in rule '" + name + "' references disabled rule '" + target_name + "'. Aborted.");
|
||||
if (!outbound_detour_target_action(target.action))
|
||||
fail_validation("Connection URL cascade in rule '" + name + "' references rule '" + target_name + "', but it is not a Connection rule. Aborted.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validate_server_routing_sections(sections) {
|
||||
let by_section = basic_rows_by_section(basic_rows_from_sections(sections));
|
||||
|
||||
@@ -1541,7 +1476,6 @@ function validate_runtime_config(context) {
|
||||
}
|
||||
|
||||
validate_outbound_detours_rows(detour_rows_from_sections(sections));
|
||||
validate_connection_url_detours(sections);
|
||||
validate_subscription_download_sections(sections, context);
|
||||
validate_server_routing_sections(sections);
|
||||
|
||||
@@ -1953,8 +1887,6 @@ else if (mode == "valid-outbound")
|
||||
exit(valid_outbound() ? 0 : 1);
|
||||
else if (mode == "valid-inbound")
|
||||
exit(valid_inbound() ? 0 : 1);
|
||||
else if (mode == "outbound-detour-supported")
|
||||
exit(outbound_detour_supported() ? 0 : 1);
|
||||
else if (mode == "validate-outbound-detours")
|
||||
validate_outbound_detours();
|
||||
else if (mode == "validate-download-section")
|
||||
|
||||
@@ -361,7 +361,6 @@ function podkop_config_masked_line(line) {
|
||||
line = mask_after_token(line, "list server_users");
|
||||
line = mask_after_token_space(line, "option dns_server");
|
||||
line = mask_after_token_space(line, "option bootstrap_dns_server");
|
||||
line = mask_after_token_space(line, "option domain_resolver_dns_server");
|
||||
line = mask_after_token_space(line, "option listen");
|
||||
line = mask_after_token_space(line, "option listen_port");
|
||||
line = mask_after_token_space(line, "option public_host");
|
||||
@@ -388,7 +387,6 @@ function podkop_config_masked_line(line) {
|
||||
line = mask_after_token_space(line, "option mixed_proxy_username");
|
||||
line = mask_after_token_space(line, "option mixed_proxy_password");
|
||||
line = mask_option_path(line, "option dns_server '");
|
||||
line = mask_option_path(line, "option domain_resolver_dns_server '");
|
||||
line = mask_after_token(line, "option yacd_secret_key");
|
||||
|
||||
return line;
|
||||
|
||||
@@ -829,16 +829,7 @@ function section_subscription_update_interval(section) {
|
||||
}
|
||||
|
||||
function connection_urls_signature(section) {
|
||||
let result = [];
|
||||
for (let entry in connections.connection_urls(section)) {
|
||||
push(result, {
|
||||
url: entry,
|
||||
outbound_detour_enabled: connections.connection_detour_enabled(section, entry) ? "1" : "0",
|
||||
outbound_detour_section: connections.connection_detour_section(section, entry),
|
||||
enable_udp_over_tcp: connections.connection_udp_over_tcp(section, entry) ? "1" : "0"
|
||||
});
|
||||
}
|
||||
return sprintf("%J", result);
|
||||
return sprintf("%J", connections.connection_urls(section));
|
||||
}
|
||||
|
||||
function subscription_urls_signature(section) {
|
||||
@@ -863,16 +854,7 @@ function subscription_urls_signature(section) {
|
||||
}
|
||||
|
||||
function interfaces_signature(section) {
|
||||
let result = [];
|
||||
for (let entry in connections.interfaces(section)) {
|
||||
push(result, {
|
||||
name: entry,
|
||||
domain_resolver_enabled: connections.interface_domain_resolver_enabled(section, entry) ? "1" : "0",
|
||||
domain_resolver_dns_type: connections.interface_domain_resolver_dns_type(section, entry),
|
||||
domain_resolver_dns_server: connections.interface_domain_resolver_dns_server(section, entry)
|
||||
});
|
||||
}
|
||||
return sprintf("%J", result);
|
||||
return sprintf("%J", connections.interfaces(section));
|
||||
}
|
||||
|
||||
function urltests_signature(section) {
|
||||
@@ -1126,6 +1108,7 @@ function append_sing_box_rule_signature_body(body, section, sections) {
|
||||
body = signature_add_value(body, prefix + ".urltest_include_outbounds", option(section, "urltest_include_outbounds", ""));
|
||||
body = signature_add_value(body, prefix + ".urltest_include_regex", option(section, "urltest_include_regex", ""));
|
||||
body = signature_add_value(body, prefix + ".subscription_update_interval", section_subscription_update_interval(section));
|
||||
body = signature_add_outbound_detour_body(body, section, prefix);
|
||||
body = signature_add_mixed_proxy_body(body, section, prefix);
|
||||
body = signature_add_value(body, prefix + ".resolve_real_ip_for_routing", bool_option_value(section, "resolve_real_ip_for_routing", false));
|
||||
}
|
||||
|
||||
@@ -68,10 +68,6 @@ function outbound_tag(section_name) {
|
||||
return tag(section_name, "out");
|
||||
}
|
||||
|
||||
function domain_resolver_tag(section_name) {
|
||||
return tag(section_name, "domain-resolver");
|
||||
}
|
||||
|
||||
function server_inbound_tag(section_name) {
|
||||
return tag("server-" + as_string(section_name), "in");
|
||||
}
|
||||
@@ -117,7 +113,6 @@ return {
|
||||
tag,
|
||||
inbound_tag,
|
||||
outbound_tag,
|
||||
domain_resolver_tag,
|
||||
server_inbound_tag,
|
||||
tailscale_dns_server_tag
|
||||
};
|
||||
|
||||
@@ -103,7 +103,7 @@ function fixture_get_section(section_name) {
|
||||
if (section_name == "settings" && type(fixture.settings) == "object")
|
||||
return fixture.settings;
|
||||
|
||||
for (let type_name in [ "settings", "server", "section", "connection_url", "subscription_url", "section_interface", "urltest", "priority_group", "priority_level" ]) {
|
||||
for (let type_name in [ "settings", "server", "section", "subscription_url", "urltest", "priority_group", "priority_level" ]) {
|
||||
for (let section in fixture_section_list(type_name)) {
|
||||
if (as_string(section[".name"]) == section_name)
|
||||
return section;
|
||||
@@ -1125,22 +1125,29 @@ function outbound_detour_tag_for_section(section) {
|
||||
return detour_section == "" ? "" : outbound_tag(detour_section);
|
||||
}
|
||||
|
||||
function apply_detour_to_outbound(config, tag_name, detour_tag) {
|
||||
function apply_section_detour_to_connection_outbounds(config, start_index, detour_tag) {
|
||||
if (detour_tag == "")
|
||||
return;
|
||||
for (let outbound in array_or_empty(config.outbounds)) {
|
||||
if (type(outbound) == "object" && outbound.tag == tag_name)
|
||||
|
||||
let outbounds = array_or_empty(config.outbounds);
|
||||
for (let i = int(start_index || 0); i < length(outbounds); i++) {
|
||||
let outbound = outbounds[i];
|
||||
if (type(outbound) != "object")
|
||||
continue;
|
||||
|
||||
let outbound_type = lc(as_string(outbound.type || ""));
|
||||
if (outbound_type == "" ||
|
||||
outbound_type == "selector" ||
|
||||
outbound_type == "urltest" || outbound_type == "dns" ||
|
||||
outbound_type == "block")
|
||||
continue;
|
||||
|
||||
// Preserve subscription chains: only their terminal dial outbound receives the section detour.
|
||||
if (as_string(outbound.detour || "") == "")
|
||||
outbound.detour = detour_tag;
|
||||
}
|
||||
}
|
||||
|
||||
function apply_detour_to_outbounds(config, tag_names, detour_tag) {
|
||||
if (detour_tag == "")
|
||||
return;
|
||||
for (let tag_name in tag_names)
|
||||
apply_detour_to_outbound(config, tag_name, detour_tag);
|
||||
}
|
||||
|
||||
function mixed_proxy_enabled_action(action) {
|
||||
return action == "connection" || action == "proxy" || action == "outbound" || action == "vpn" ||
|
||||
action == "byedpi" || action == "zapret" || action == "zapret2";
|
||||
@@ -1451,7 +1458,7 @@ function manual_http_outbound(link, tag_name) {
|
||||
return outbound;
|
||||
}
|
||||
|
||||
function manual_socks_outbound(link, tag_name, udp_over_tcp) {
|
||||
function manual_socks_outbound(link, tag_name) {
|
||||
let scheme = url_scheme(link);
|
||||
let host = url_host(link);
|
||||
let port = parse_port(url_port(link));
|
||||
@@ -1474,12 +1481,10 @@ function manual_socks_outbound(link, tag_name, udp_over_tcp) {
|
||||
outbound.password = substr(userinfo, colon + 1);
|
||||
}
|
||||
}
|
||||
if (udp_over_tcp)
|
||||
outbound.udp_over_tcp = { enabled: true, version: 2 };
|
||||
return outbound;
|
||||
}
|
||||
|
||||
function manual_shadowsocks_outbound(link, tag_name, udp_over_tcp) {
|
||||
function manual_shadowsocks_outbound(link, tag_name) {
|
||||
let raw = url_strip_fragment_value(url_decode(link));
|
||||
let body = substr(raw, 5);
|
||||
let question = index(body, "?");
|
||||
@@ -1534,8 +1539,6 @@ function manual_shadowsocks_outbound(link, tag_name, udp_over_tcp) {
|
||||
outbound.plugin = as_string(query.plugin);
|
||||
if (as_string(query["plugin-opts"] || "") != "")
|
||||
outbound.plugin_opts = as_string(query["plugin-opts"]);
|
||||
if (udp_over_tcp)
|
||||
outbound.udp_over_tcp = { enabled: true, version: 2 };
|
||||
return outbound;
|
||||
}
|
||||
|
||||
@@ -1696,7 +1699,7 @@ function manual_hysteria2_outbound(link, tag_name) {
|
||||
return outbound;
|
||||
}
|
||||
|
||||
function manual_link_outbound(link, tag_name, udp_over_tcp) {
|
||||
function manual_link_outbound(link, tag_name) {
|
||||
let scheme = url_scheme(link);
|
||||
if (scheme == "vmess")
|
||||
return manual_vmess_outbound(link, tag_name);
|
||||
@@ -1706,9 +1709,9 @@ function manual_link_outbound(link, tag_name, udp_over_tcp) {
|
||||
if (scheme == "http" || scheme == "https")
|
||||
return manual_http_outbound(link, tag_name);
|
||||
if (scheme == "socks4" || scheme == "socks4a" || scheme == "socks5")
|
||||
return manual_socks_outbound(link, tag_name, udp_over_tcp);
|
||||
return manual_socks_outbound(link, tag_name);
|
||||
if (scheme == "ss")
|
||||
return manual_shadowsocks_outbound(link, tag_name, udp_over_tcp);
|
||||
return manual_shadowsocks_outbound(link, tag_name);
|
||||
if (scheme == "vless")
|
||||
return manual_vless_outbound(link, tag_name);
|
||||
if (scheme == "trojan")
|
||||
@@ -1718,13 +1721,13 @@ function manual_link_outbound(link, tag_name, udp_over_tcp) {
|
||||
runtime_generate_unsupported("manual proxy link scheme is not supported by sing-box config generation yet");
|
||||
}
|
||||
|
||||
function add_manual_proxy_link(config, state, section_name, manual_index, link, udp_over_tcp, taken, selector_tags, urltest_candidate_tags) {
|
||||
function add_manual_proxy_link(config, state, section_name, manual_index, link, taken, selector_tags, urltest_candidate_tags) {
|
||||
let tag_name = outbound_tag(section_name + "-" + manual_index);
|
||||
if (taken[tag_name])
|
||||
tag_name = unique_tag(tag_name, taken);
|
||||
taken[tag_name] = true;
|
||||
|
||||
let outbound = manual_link_outbound(link, tag_name, udp_over_tcp);
|
||||
let outbound = manual_link_outbound(link, tag_name);
|
||||
push(config.outbounds, outbound);
|
||||
push(selector_tags, tag_name);
|
||||
push(urltest_candidate_tags, tag_name);
|
||||
@@ -1743,31 +1746,21 @@ function connection_item_tag(section_name, kind, item_index) {
|
||||
return outbound_tag(section_name + "-" + as_string(kind) + "-" + item_index);
|
||||
}
|
||||
|
||||
function connection_detour_tag(section, link) {
|
||||
if (!connections.connection_detour_enabled(section, link))
|
||||
return "";
|
||||
|
||||
let detour_section = connections.connection_detour_section(section, link);
|
||||
return detour_section == "" ? "" : outbound_tag(detour_section);
|
||||
}
|
||||
|
||||
function add_connection_manual_links(config, state, section, taken, selector_tags, urltest_candidate_tags) {
|
||||
let section_name = section[".name"];
|
||||
let manual_links = connections.connection_urls(section);
|
||||
for (let i = 0; i < length(manual_links); i++) {
|
||||
let link = manual_links[i];
|
||||
let tag_name = add_manual_proxy_link(
|
||||
add_manual_proxy_link(
|
||||
config,
|
||||
state,
|
||||
section_name,
|
||||
i + 1,
|
||||
link,
|
||||
connections.connection_udp_over_tcp(section, link),
|
||||
taken,
|
||||
selector_tags,
|
||||
urltest_candidate_tags
|
||||
);
|
||||
apply_detour_to_outbound(config, tag_name, connection_detour_tag(section, link));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1798,29 +1791,12 @@ function add_interface_connection_outbound(config, state, section, interface_ind
|
||||
tag_name = unique_tag(tag_name, taken);
|
||||
taken[tag_name] = true;
|
||||
|
||||
let domain_resolver = "";
|
||||
if (connections.interface_domain_resolver_enabled(section, interface_name)) {
|
||||
domain_resolver = runtime_constants.domain_resolver_tag(section_name + "-interface-" + interface_index);
|
||||
let dns_server = runtime_dns.server_from_options(
|
||||
domain_resolver,
|
||||
connections.interface_domain_resolver_dns_type(section, interface_name),
|
||||
connections.interface_domain_resolver_dns_server(section, interface_name),
|
||||
tag_name
|
||||
);
|
||||
if (dns_server.unsupported)
|
||||
runtime_generate_unsupported(dns_server.unsupported);
|
||||
push(config.dns.servers, dns_server);
|
||||
}
|
||||
|
||||
let outbound = {
|
||||
type: "direct",
|
||||
tag: tag_name,
|
||||
bind_interface: interface_name,
|
||||
domain_resolver,
|
||||
routing_mark: runtime_constants.OUTBOUND_MARK
|
||||
};
|
||||
if (domain_resolver == "")
|
||||
delete outbound.domain_resolver;
|
||||
|
||||
push(config.outbounds, outbound);
|
||||
push(selector_tags, tag_name);
|
||||
@@ -1876,9 +1852,16 @@ function add_connections_outbound(config, section, taken) {
|
||||
let selector_tags = [];
|
||||
let urltest_candidate_tags = [];
|
||||
let state = runtime_subscription.new_section_state(section_name);
|
||||
let cascade_start = length(array_or_empty(config.outbounds));
|
||||
|
||||
add_connection_manual_links(config, state, section, taken, selector_tags, urltest_candidate_tags);
|
||||
add_connection_subscriptions(config, state, section, taken, selector_tags, urltest_candidate_tags);
|
||||
// Apply before interface and JSON items are added: those source kinds are intentionally excluded.
|
||||
apply_section_detour_to_connection_outbounds(
|
||||
config,
|
||||
cascade_start,
|
||||
outbound_detour_tag_for_section(section)
|
||||
);
|
||||
add_connection_interfaces(config, state, section, taken, selector_tags, urltest_candidate_tags);
|
||||
add_connection_json_outbounds(config, state, section, taken, selector_tags, urltest_candidate_tags);
|
||||
|
||||
@@ -1923,9 +1906,6 @@ function add_json_outbound(config, section) {
|
||||
runtime_generate_unsupported("JSON outbound is not an object");
|
||||
|
||||
outbound.tag = outbound_tag(section[".name"]);
|
||||
let detour_tag = outbound_detour_tag_for_section(section);
|
||||
if (detour_tag != "")
|
||||
outbound.detour = detour_tag;
|
||||
push(config.outbounds, outbound);
|
||||
}
|
||||
|
||||
@@ -1934,29 +1914,12 @@ function add_vpn_outbound(config, section) {
|
||||
if (interface_name == "")
|
||||
runtime_generate_unsupported("VPN interface is not set");
|
||||
|
||||
let domain_resolver = "";
|
||||
if (bool_option(section, "domain_resolver_enabled", false)) {
|
||||
domain_resolver = runtime_constants.domain_resolver_tag(section[".name"]);
|
||||
let dns_server = runtime_dns.server_from_options(
|
||||
domain_resolver,
|
||||
option(section, "domain_resolver_dns_type", ""),
|
||||
option(section, "domain_resolver_dns_server", ""),
|
||||
outbound_tag(section[".name"])
|
||||
);
|
||||
if (dns_server.unsupported)
|
||||
runtime_generate_unsupported(dns_server.unsupported);
|
||||
push(config.dns.servers, dns_server);
|
||||
}
|
||||
|
||||
push(config.outbounds, {
|
||||
type: "direct",
|
||||
tag: outbound_tag(section[".name"]),
|
||||
bind_interface: interface_name,
|
||||
domain_resolver,
|
||||
routing_mark: runtime_constants.OUTBOUND_MARK
|
||||
});
|
||||
if (domain_resolver == "")
|
||||
delete config.outbounds[length(config.outbounds) - 1].domain_resolver;
|
||||
}
|
||||
|
||||
function enabled_action_index(sections, target_section, action_name) {
|
||||
|
||||
@@ -43,6 +43,9 @@ const fixture = {
|
||||
connection_type: 'proxy',
|
||||
proxy_config_type: 'url',
|
||||
proxy_string: 'vless://one\n//commented\n ss://two ',
|
||||
enable_udp_over_tcp: '1',
|
||||
outbound_detour_enabled: '1',
|
||||
outbound_detour_section: 'legacy-sub',
|
||||
urltest_check_interval_disabled: '1',
|
||||
domain: [ 'Example.COM', 'full:Already.EXAMPLE' ],
|
||||
domain_keyword_text_mode: '1',
|
||||
@@ -78,7 +81,7 @@ const fixture = {
|
||||
{
|
||||
'.name': 'legacy-list-sub',
|
||||
'.type': 'section',
|
||||
action: 'connection',
|
||||
action: 'proxy',
|
||||
subscription_urls: [
|
||||
'https://example.com/list.txt | ListAgent/2.0',
|
||||
'https://example.com/auto.txt'
|
||||
@@ -113,6 +116,14 @@ const fixture = {
|
||||
domain_resolver_enabled: '1',
|
||||
domain_resolver_dns_type: 'doh',
|
||||
domain_resolver_dns_server: 'https://dns.example/dns-query'
|
||||
},
|
||||
{
|
||||
'.name': 'legacy-outbound',
|
||||
'.type': 'section',
|
||||
action: 'outbound',
|
||||
outbound_json: '{"type":"socks","server":"127.0.0.1","server_port":1080,"version":"5"}',
|
||||
outbound_detour_enabled: '1',
|
||||
outbound_detour_section: 'legacy-url'
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -128,9 +139,7 @@ const out = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
||||
const config = out.config;
|
||||
const sections = Object.fromEntries(config.section.map(section => [section['.name'], section]));
|
||||
const childByType = type => config[type] || [];
|
||||
const connectionUrls = childByType('connection_url');
|
||||
const subscriptionUrls = childByType('subscription_url');
|
||||
const interfaces = childByType('section_interface');
|
||||
const urltests = childByType('urltest');
|
||||
|
||||
function assert(condition, message) {
|
||||
@@ -175,7 +184,10 @@ absent(config.settings, 'routing_excluded_ips', 'settings');
|
||||
const legacyUrl = sections['legacy-url'];
|
||||
assert(legacyUrl['.type'] === 'section', 'legacy rule should become section');
|
||||
assert(legacyUrl.action === 'connection', 'legacy-url action');
|
||||
assert(JSON.stringify(childValues(legacyUrl, connectionUrls, 'url')) === JSON.stringify(['vless://one', 'ss://two']), 'proxy_string links');
|
||||
assert(JSON.stringify(legacyUrl.selector_proxy_links) === JSON.stringify(['vless://one', 'ss://two']), 'proxy_string links');
|
||||
assert(legacyUrl.outbound_detour_enabled === '1', 'section cascade enabled preserved');
|
||||
assert(legacyUrl.outbound_detour_section === 'legacy-sub', 'section cascade target preserved');
|
||||
absent(legacyUrl, 'enable_udp_over_tcp', 'legacy-url');
|
||||
const legacyUrlDomains = (legacyUrl.domain || '').split(/\s+/).filter(Boolean);
|
||||
assert(legacyUrlDomains.includes('full:Example.COM'), 'full domain list value migrated');
|
||||
assert(legacyUrlDomains.includes('full:Already.EXAMPLE'), 'prefixed full domain list value migrated without duplicate prefix');
|
||||
@@ -187,13 +199,11 @@ assert(legacyUrlDomains.includes('regex:^cdn[.]example$'), 'second regex migrate
|
||||
assert(JSON.stringify(legacyUrl.rule_set) === JSON.stringify(['https://example.com/domains.srs']), 'domain rule set preserved');
|
||||
assert(JSON.stringify(legacyUrl.rule_set_with_subnets) === JSON.stringify(['https://example.com/mixed.srs']), 'rule set subnet list preserved');
|
||||
absent(legacyUrl, 'proxy_string', 'legacy-url');
|
||||
absent(legacyUrl, 'selector_proxy_links', 'legacy-url');
|
||||
absent(legacyUrl, 'proxy_config_type', 'legacy-url');
|
||||
absent(legacyUrl, 'connection_type', 'legacy-url');
|
||||
absent(legacyUrl, 'urltest_enabled', 'legacy-url');
|
||||
absent(legacyUrl, 'rule_set_settings', 'legacy-url');
|
||||
absent(legacyUrl, 'domain_suffix', 'legacy-url');
|
||||
absent(legacyUrl, 'connection_url_items', 'legacy-url');
|
||||
absent(legacyUrl, 'rule_set_items', 'legacy-url');
|
||||
absent(legacyUrl, 'domain_keyword_text', 'legacy-url');
|
||||
absent(legacyUrl, 'domain_regex_text', 'legacy-url');
|
||||
@@ -251,7 +261,7 @@ absent(legacyListSub, 'subscription_url_items', 'legacy-list-sub');
|
||||
|
||||
const legacyUrltest = sections['legacy-urltest'];
|
||||
assert(legacyUrltest.action === 'connection', 'legacy-urltest action');
|
||||
assert(JSON.stringify(childValues(legacyUrltest, connectionUrls, 'url')) === JSON.stringify(['vmess://a', 'trojan://b']), 'urltest links deduped');
|
||||
assert(JSON.stringify(legacyUrltest.selector_proxy_links) === JSON.stringify(['vmess://a', 'trojan://b']), 'urltest links deduped');
|
||||
absent(legacyUrltest, 'urltests', 'legacy-urltest');
|
||||
const legacyUrltestConfig = urltestByOwner(legacyUrltest);
|
||||
assert(Boolean(legacyUrltestConfig), 'legacy-urltest URLTest child migrated');
|
||||
@@ -262,8 +272,6 @@ assert(legacyUrltestConfig.filter_mode === 'exclude', 'legacy-urltest URLTest fi
|
||||
assert(legacyUrltestConfig.detect_server_country === 'flag_emoji', 'legacy-urltest detect server country normalized');
|
||||
assert(JSON.stringify(legacyUrltestConfig.exclude_regex) === JSON.stringify(['bad.*']), 'legacy-urltest exclude regex migrated');
|
||||
absent(legacyUrltest, 'urltest_proxy_links', 'legacy-urltest');
|
||||
absent(legacyUrltest, 'selector_proxy_links', 'legacy-urltest');
|
||||
absent(legacyUrltest, 'connection_url_items', 'legacy-urltest');
|
||||
absent(legacyUrltest, 'urltest_enabled', 'legacy-urltest');
|
||||
absent(legacyUrltest, 'urltest_filter_mode', 'legacy-urltest');
|
||||
absent(legacyUrltest, 'detect_server_country', 'legacy-urltest');
|
||||
@@ -283,17 +291,20 @@ absent(legacyZap, 'cmd_opts', 'legacy-zap');
|
||||
|
||||
const legacyVpn = sections['legacy-vpn'];
|
||||
assert(legacyVpn.action === 'connection', 'vpn action inferred');
|
||||
assert(JSON.stringify(childValues(legacyVpn, interfaces, 'name')) === JSON.stringify(['awg0']), 'vpn interface migrated to interface item');
|
||||
const legacyVpnInterface = childObjects(legacyVpn, interfaces)[0];
|
||||
assert(legacyVpnInterface.domain_resolver_enabled === '1', 'vpn domain resolver enabled migrated');
|
||||
assert(legacyVpnInterface.domain_resolver_dns_type === 'doh', 'vpn domain resolver type migrated');
|
||||
assert(legacyVpnInterface.domain_resolver_dns_server === 'https://dns.example/dns-query', 'vpn domain resolver server migrated');
|
||||
assert(JSON.stringify(legacyVpn.interfaces) === JSON.stringify(['awg0']), 'vpn interface migrated to section list');
|
||||
absent(legacyVpn, 'proxy_config_type', 'legacy-vpn');
|
||||
absent(legacyVpn, 'interface', 'legacy-vpn');
|
||||
absent(legacyVpn, 'interfaces', 'legacy-vpn');
|
||||
absent(legacyVpn, 'interface_items', 'legacy-vpn');
|
||||
absent(legacyVpn, 'interface_settings', 'legacy-vpn');
|
||||
absent(legacyVpn, 'domain_resolver_enabled', 'legacy-vpn');
|
||||
absent(legacyVpn, 'domain_resolver_dns_type', 'legacy-vpn');
|
||||
absent(legacyVpn, 'domain_resolver_dns_server', 'legacy-vpn');
|
||||
|
||||
const legacyOutbound = sections['legacy-outbound'];
|
||||
assert(legacyOutbound.action === 'connection', 'outbound action inferred');
|
||||
assert(Array.isArray(legacyOutbound.outbound_jsons) && legacyOutbound.outbound_jsons.length === 1, 'legacy JSON outbound migrated to list');
|
||||
assert(JSON.parse(legacyOutbound.outbound_jsons[0]).detour === 'legacy-url-out', 'legacy JSON cascade embedded in outbound');
|
||||
absent(legacyOutbound, 'outbound_detour_enabled', 'legacy-outbound');
|
||||
absent(legacyOutbound, 'outbound_detour_section', 'legacy-outbound');
|
||||
|
||||
assert(out.removed_caches.includes('/tmp/sing-box/subscriptions/legacy-sub.json'), 'subscription runtime cache removal');
|
||||
assert(out.removed_caches.includes('/var/run/podkop-plus/section-cache/legacy-sub.json'), 'section cache removal');
|
||||
@@ -329,16 +340,10 @@ grep -Fxq 'podkop-plus.legacy.action=connection' "$WORK_DIR/runtime-migrate.stat
|
||||
grep -Fxq 'podkop-plus.old_direct.action=bypass' "$WORK_DIR/runtime-migrate.state" ||
|
||||
fail "runtime migration must convert direct action to bypass through core.uci"
|
||||
grep -Eq '^podkop-plus\\.cfg[0-9a-f]+=$' "$WORK_DIR/runtime-migrate.state" && fail "anonymous fixture section should include a type"
|
||||
child_section="$(awk -F= '$2 == "connection_url" { sub(/^podkop-plus[.]/, "", $1); print $1; exit }' "$WORK_DIR/runtime-migrate.state")"
|
||||
if [ -z "$child_section" ]; then
|
||||
fail "runtime migration must create connection_url child section through core.uci"
|
||||
fi
|
||||
grep -Fxq "podkop-plus.${child_section}.section=legacy" "$WORK_DIR/runtime-migrate.state" ||
|
||||
fail "runtime migration must link anonymous connection_url child through section option"
|
||||
grep -Fxq "podkop-plus.${child_section}.url=vless://one" "$WORK_DIR/runtime-migrate.state" ||
|
||||
fail "runtime migration must write connection_url child value through core.uci"
|
||||
if grep -Fq 'podkop-plus.legacy.connection_url_items=' "$WORK_DIR/runtime-migrate.state"; then
|
||||
fail "runtime migration must not write parent child-reference lists"
|
||||
grep -Fxq 'podkop-plus.legacy.selector_proxy_links=vless://one' "$WORK_DIR/runtime-migrate.state" ||
|
||||
fail "runtime migration must keep connection URLs in the parent section"
|
||||
if grep -Fq '=connection_url' "$WORK_DIR/runtime-migrate.state"; then
|
||||
fail "runtime migration must not create connection_url child sections"
|
||||
fi
|
||||
if grep -Fq 'podkop-plus.legacy.urltest_enabled=' "$WORK_DIR/runtime-migrate.state"; then
|
||||
fail "runtime migration must delete migrated urltest_enabled through core.uci"
|
||||
|
||||
@@ -11,7 +11,7 @@ fail() {
|
||||
}
|
||||
|
||||
rows() {
|
||||
printf '%s\t%s\t%s\t%s\t%s\t%s\n' "$@"
|
||||
printf '%s\t%s\t%s\t%s\t%s\n' "$@"
|
||||
}
|
||||
|
||||
assert_rejects() {
|
||||
@@ -29,39 +29,30 @@ assert_rejects() {
|
||||
}
|
||||
|
||||
rows \
|
||||
source 1 proxy 1 target '' \
|
||||
target 1 vpn 0 '' '' |
|
||||
ucode -L "$PODKOP_LIB" "$VALIDATOR" validate-outbound-detours
|
||||
|
||||
rows \
|
||||
source 1 outbound 1 target '{"type":"direct"}' \
|
||||
target 1 proxy 0 '' '' |
|
||||
source 1 connection 1 target \
|
||||
target 1 connection 0 '' |
|
||||
ucode -L "$PODKOP_LIB" "$VALIDATOR" validate-outbound-detours
|
||||
|
||||
assert_rejects "unsupported source action" "supported only for Connection rules" \
|
||||
source 1 zapret 1 target '' \
|
||||
target 1 proxy 0 '' ''
|
||||
source 1 zapret 1 target \
|
||||
target 1 connection 0 ''
|
||||
|
||||
assert_rejects "missing target" "references missing rule 'missing'" \
|
||||
source 1 proxy 1 missing ''
|
||||
source 1 connection 1 missing
|
||||
|
||||
assert_rejects "disabled target" "references disabled rule 'target'" \
|
||||
source 1 proxy 1 target '' \
|
||||
target 0 proxy 0 '' ''
|
||||
source 1 connection 1 target \
|
||||
target 0 connection 0 ''
|
||||
|
||||
assert_rejects "wrong target action" "but it is not a Connection rule" \
|
||||
source 1 proxy 1 target '' \
|
||||
target 1 zapret 0 '' ''
|
||||
source 1 connection 1 target \
|
||||
target 1 zapret 0 ''
|
||||
|
||||
assert_rejects "self target" "cannot point to itself" \
|
||||
source 1 proxy 1 source ''
|
||||
source 1 connection 1 source
|
||||
|
||||
assert_rejects "cycle" "creates a cycle through 'second'" \
|
||||
first 1 proxy 1 second '' \
|
||||
second 1 proxy 1 first ''
|
||||
|
||||
assert_rejects "unsupported json outbound" "does not support Dial Fields" \
|
||||
source 1 outbound 1 target '{"type":"selector"}' \
|
||||
target 1 proxy 0 '' ''
|
||||
first 1 connection 1 second \
|
||||
second 1 connection 1 first
|
||||
|
||||
printf 'config validator detour regression checks passed\n'
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
PODKOP_LIB="$ROOT_DIR/podkop/files/usr/lib"
|
||||
GENERATOR="$PODKOP_LIB/singbox/generator.uc"
|
||||
WORK_DIR="$(mktemp -d)"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$WORK_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
fail() {
|
||||
printf 'FAIL: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
mkdir -p "$WORK_DIR/subscriptions" "$WORK_DIR/output.json.section-cache"
|
||||
|
||||
cat >"$WORK_DIR/subscriptions/source-subscription-1.json" <<'JSON'
|
||||
{
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "vless",
|
||||
"tag": "nested-entry",
|
||||
"server": "192.0.2.10",
|
||||
"server_port": 443,
|
||||
"uuid": "00000000-0000-4000-8000-000000000001",
|
||||
"detour": "nested-relay"
|
||||
},
|
||||
{
|
||||
"type": "socks",
|
||||
"tag": "nested-relay",
|
||||
"server": "192.0.2.11",
|
||||
"server_port": 1080,
|
||||
"version": "5"
|
||||
},
|
||||
{
|
||||
"type": "trojan",
|
||||
"tag": "independent",
|
||||
"server": "192.0.2.12",
|
||||
"server_port": 443,
|
||||
"password": "secret"
|
||||
},
|
||||
{
|
||||
"type": "urltest",
|
||||
"tag": "provider-group",
|
||||
"outbounds": [ "nested-entry", "independent" ],
|
||||
"url": "https://www.gstatic.com/generate_204",
|
||||
"interval": "3m",
|
||||
"__podkop_allow_group": true
|
||||
}
|
||||
]
|
||||
}
|
||||
JSON
|
||||
printf '%s\n' 'https://subscription.example/source' >"$WORK_DIR/subscriptions/source-subscription-1.url"
|
||||
: >"$WORK_DIR/subscriptions/source-subscription-1.user_agent"
|
||||
|
||||
cat >"$WORK_DIR/fixture.json" <<'JSON'
|
||||
{
|
||||
"settings": {
|
||||
".name": "settings",
|
||||
".type": "settings",
|
||||
"dns_server": "1.1.1.1",
|
||||
"service_listen_address": "127.0.0.1"
|
||||
},
|
||||
"section": [
|
||||
{
|
||||
".name": "hop",
|
||||
".type": "section",
|
||||
"enabled": "1",
|
||||
"action": "connection",
|
||||
"selector_proxy_links": [ "socks5://127.0.0.1:1080#Hop" ]
|
||||
},
|
||||
{
|
||||
".name": "source",
|
||||
".type": "section",
|
||||
"enabled": "1",
|
||||
"action": "connection",
|
||||
"selector_proxy_links": [ "socks5://127.0.0.1:1081#Manual" ],
|
||||
"interfaces": [ "wg0" ],
|
||||
"outbound_jsons": [
|
||||
"{\"type\":\"socks\",\"tag\":\"JSON plain\",\"server\":\"127.0.0.1\",\"server_port\":1082,\"version\":\"5\"}",
|
||||
"{\"type\":\"socks\",\"tag\":\"JSON explicit\",\"server\":\"127.0.0.1\",\"server_port\":1083,\"version\":\"5\",\"detour\":\"direct-out\"}"
|
||||
],
|
||||
"outbound_detour_enabled": "1",
|
||||
"outbound_detour_section": "hop"
|
||||
}
|
||||
],
|
||||
"subscription_url": [
|
||||
{
|
||||
".name": "source_subscription",
|
||||
".type": "subscription_url",
|
||||
"section": "source",
|
||||
"url": "https://subscription.example/source"
|
||||
}
|
||||
]
|
||||
}
|
||||
JSON
|
||||
|
||||
TMP_SUBSCRIPTION_FOLDER="$WORK_DIR/subscriptions" \
|
||||
ucode -L "$PODKOP_LIB" "$GENERATOR" generate-config-fixture \
|
||||
"$WORK_DIR/fixture.json" "$WORK_DIR/output.json" "127.0.0.1"
|
||||
|
||||
ucode -e '
|
||||
let fs = require("fs");
|
||||
let config = json(fs.readfile(ARGV[0]));
|
||||
|
||||
function outbound(tag) {
|
||||
for (let item in config.outbounds || [])
|
||||
if (item && item.tag == tag)
|
||||
return item;
|
||||
return null;
|
||||
}
|
||||
|
||||
function assert(condition, message) {
|
||||
if (!condition)
|
||||
die(message + "\n");
|
||||
}
|
||||
|
||||
assert(outbound("source-1-out").detour == "hop-out", "manual URL did not receive the section detour");
|
||||
assert(outbound("nested-entry").detour == "nested-relay", "subscription detour was overwritten");
|
||||
assert(outbound("nested-relay").detour == "hop-out", "section detour was not appended to the subscription chain");
|
||||
assert(outbound("independent").detour == "hop-out", "independent subscription outbound did not receive the section detour");
|
||||
assert(outbound("provider-group").detour == null, "subscription URLTest group must not receive Dial Fields");
|
||||
assert(outbound("source-interface-1-out").bind_interface == "wg0", "interface binding was not preserved");
|
||||
assert(outbound("source-interface-1-out").detour == null, "network interface must be excluded from section cascade");
|
||||
assert(outbound("source-json-1-out").detour == null, "plain JSON outbound must be excluded from section cascade");
|
||||
assert(outbound("source-json-2-out").detour == "direct-out", "explicit JSON detour was not preserved");
|
||||
assert(outbound("source-out").detour == null, "section selector must not receive its own detour");
|
||||
' "$WORK_DIR/output.json" || fail "section cascade generation"
|
||||
|
||||
printf 'connection cascade regression checks passed\n'
|
||||
@@ -314,7 +314,6 @@ cat >"$WORK_DIR/sing-box-signature.json" <<'JSON'
|
||||
"action": "proxy",
|
||||
"selector_proxy_links": [ "vless://one", "vless://two" ],
|
||||
"subscription_urls": [ "https://example.com/sub.txt" ],
|
||||
"enable_udp_over_tcp": "yes",
|
||||
"urltest_enabled": "1",
|
||||
"detect_server_country": "1",
|
||||
"urltest_tolerance": "75",
|
||||
@@ -367,9 +366,6 @@ cat >"$WORK_DIR/sing-box-signature.json" <<'JSON'
|
||||
"enabled": "1",
|
||||
"action": "vpn",
|
||||
"interface": "wg0",
|
||||
"domain_resolver_enabled": "1",
|
||||
"domain_resolver_dns_type": "udp",
|
||||
"domain_resolver_dns_server": "8.8.8.8",
|
||||
"mixed_proxy_enabled": "0",
|
||||
"resolve_real_ip_for_routing": "yes"
|
||||
}
|
||||
@@ -434,7 +430,7 @@ secret
|
||||
[rule.proxy1.action]
|
||||
proxy
|
||||
[rule.proxy1.connection_urls]
|
||||
[ { "url": "vless://one", "outbound_detour_enabled": "1", "outbound_detour_section": "out1", "enable_udp_over_tcp": "1" }, { "url": "vless://two", "outbound_detour_enabled": "1", "outbound_detour_section": "out1", "enable_udp_over_tcp": "1" } ]
|
||||
[ "vless://one", "vless://two" ]
|
||||
[rule.proxy1.subscription_urls]
|
||||
[ { "url": "https://example.com/sub.txt", "subscription_update_enabled": "1", "subscription_update_interval": "1h", "download_via_proxy_section": "", "auto_user_agent": "1", "user_agent": "", "auto_hwid": "1", "hwid": "", "show_dashboard_metadata": "1", "include_urltest_groups": "1", "hide_urltest_group_outbounds": "1", "hide_detour_outbounds": "1" } ]
|
||||
[rule.proxy1.interfaces]
|
||||
@@ -475,6 +471,10 @@ node-a
|
||||
|
||||
[rule.proxy1.subscription_update_interval]
|
||||
1h
|
||||
[rule.proxy1.outbound_detour_enabled]
|
||||
1
|
||||
[rule.proxy1.outbound_detour_section]
|
||||
out1
|
||||
[rule.proxy1.mixed_proxy_enabled]
|
||||
1
|
||||
[rule.proxy1.mixed_proxy_port]
|
||||
@@ -555,6 +555,8 @@ disabled
|
||||
|
||||
[rule.out1.subscription_update_interval]
|
||||
|
||||
[rule.out1.outbound_detour_enabled]
|
||||
0
|
||||
[rule.out1.mixed_proxy_enabled]
|
||||
0
|
||||
[rule.out1.resolve_real_ip_for_routing]
|
||||
@@ -622,7 +624,7 @@ vpn
|
||||
[rule.vpn1.subscription_urls]
|
||||
[ ]
|
||||
[rule.vpn1.interfaces]
|
||||
[ { "name": "wg0", "domain_resolver_enabled": "1", "domain_resolver_dns_type": "udp", "domain_resolver_dns_server": "8.8.8.8" } ]
|
||||
[ "wg0" ]
|
||||
[rule.vpn1.outbound_jsons]
|
||||
|
||||
[rule.vpn1.legacy_interface]
|
||||
@@ -659,6 +661,8 @@ disabled
|
||||
|
||||
[rule.vpn1.subscription_update_interval]
|
||||
|
||||
[rule.vpn1.outbound_detour_enabled]
|
||||
0
|
||||
[rule.vpn1.mixed_proxy_enabled]
|
||||
0
|
||||
[rule.vpn1.resolve_real_ip_for_routing]
|
||||
|
||||
@@ -270,7 +270,8 @@ cat >"$WORK_DIR/runtime-matchers-fixture.json" <<'JSON'
|
||||
"selector_proxy_links": [ "socks5://127.0.0.1:1080" ],
|
||||
"domain_suffix": [ "proxy.example.org", "сайт.рф", "full:пример.испытание", "keyword:пример", "regex:^сайт[.]рф$" ],
|
||||
"source_ip_cidr": [ "10.0.0.2/32", "2001:db8::2/128" ],
|
||||
"connection_url_settings": "{\"socks5://127.0.0.1:1080\":{\"outbound_detour_enabled\":\"1\",\"outbound_detour_section\":\"detour\"}}",
|
||||
"outbound_detour_enabled": "1",
|
||||
"outbound_detour_section": "detour",
|
||||
"mixed_proxy_enabled": "1",
|
||||
"mixed_proxy_port": "19090",
|
||||
"mixed_proxy_auth_enabled": "1",
|
||||
@@ -352,7 +353,7 @@ cat >"$WORK_DIR/manual-transport-fixture.json" <<'JSON'
|
||||
}
|
||||
JSON
|
||||
|
||||
cat >"$WORK_DIR/vpn-domain-resolver-fixture.json" <<'JSON'
|
||||
cat >"$WORK_DIR/vpn-interface-fixture.json" <<'JSON'
|
||||
{
|
||||
"settings": {
|
||||
".name": "settings",
|
||||
@@ -368,7 +369,6 @@ cat >"$WORK_DIR/vpn-domain-resolver-fixture.json" <<'JSON'
|
||||
"enabled": "1",
|
||||
"action": "connection",
|
||||
"interfaces": [ "tun0" ],
|
||||
"interface_settings": "{\"tun0\":{\"domain_resolver_enabled\":\"1\",\"domain_resolver_dns_type\":\"doh\",\"domain_resolver_dns_server\":\"https://dns.example/dns-query\"}}",
|
||||
"domain_suffix": [ "vpn.example" ]
|
||||
}
|
||||
]
|
||||
@@ -681,7 +681,7 @@ generate_config "$WORK_DIR/runtime-matchers-fixture.json" "$WORK_DIR/matchers.js
|
||||
generate_config "$WORK_DIR/urltest-filter-fixture.json" "$WORK_DIR/urltest.json"
|
||||
generate_config "$WORK_DIR/provider-actions-fixture.json" "$WORK_DIR/providers.json"
|
||||
generate_config "$WORK_DIR/manual-transport-fixture.json" "$WORK_DIR/manual.json"
|
||||
generate_config "$WORK_DIR/vpn-domain-resolver-fixture.json" "$WORK_DIR/vpn.json"
|
||||
generate_config "$WORK_DIR/vpn-interface-fixture.json" "$WORK_DIR/vpn.json"
|
||||
generate_config "$WORK_DIR/download-via-proxy-fixture.json" "$WORK_DIR/download.json"
|
||||
generate_config "$WORK_DIR/fully-routed-fixture.json" "$WORK_DIR/fully-routed.json"
|
||||
generate_config "$WORK_DIR/mwan3-auto-fixture.json" "$WORK_DIR/mwan3-auto.json" 1
|
||||
@@ -844,8 +844,7 @@ assert(outbound(manual, "proxy-3-out").type == "shadowsocks", "manual Shadowsock
|
||||
|
||||
let vpn = cfg("vpn");
|
||||
assert(outbound(vpn, "renamed_awg-interface-1-out").bind_interface == "tun0", "renamed VPN interface outbound");
|
||||
assert(outbound(vpn, "renamed_awg-interface-1-out").domain_resolver == "renamed_awg-interface-1-domain-resolver", "renamed VPN domain resolver tag");
|
||||
assert(dns_server(vpn, r => r.tag == "renamed_awg-interface-1-domain-resolver") != null, "renamed VPN domain resolver DNS server");
|
||||
assert(outbound(vpn, "renamed_awg-interface-1-out").detour == null, "interface outbound must not receive a detour");
|
||||
assert(route_rule(vpn, r => r.outbound == "renamed_awg-out" && contains(r.domain_suffix, "vpn.example")) != null, "renamed VPN custom domain route");
|
||||
assert(dns_rule(vpn, r => contains(r.domain_suffix, "vpn.example")) != null, "renamed VPN custom domain DNS rule");
|
||||
|
||||
|
||||
@@ -189,8 +189,7 @@ cat >"$WORK_DIR/hide-added-fixture.json" <<'JSON'
|
||||
{
|
||||
"settings": {
|
||||
"interface": "lan",
|
||||
"routing_mode": "proxy",
|
||||
"domain_resolver_enabled": "0"
|
||||
"routing_mode": "proxy"
|
||||
},
|
||||
"section": [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user