Compare commits

...

57 Commits

Author SHA1 Message Date
itdoginfo
f960358eb6 0.3.36 2025-04-25 10:57:59 +03:00
itdoginfo
ba44966c02 Interface trigger. Disable sing-box autostart. dont touch dhcp. reload without dnsmasq restart 2025-04-24 19:25:08 +03:00
itdoginfo
615241aa37 Merge pull request #88 from Davoyan/patch-1
Update localisation
2025-04-22 11:36:38 +03:00
Davoyan
9a3220d226 Update localisation 2025-04-22 11:24:54 +03:00
itdoginfo
ec8d28857e #82 and #83 2025-04-15 00:42:16 +03:00
itdoginfo
26b49f5bbb Check fix 2025-04-15 00:15:28 +03:00
itdoginfo
0a7efb3169 Fix 2025-04-03 17:53:21 +03:00
itdoginfo
468e51ee8e v0.3.35 2025-04-03 17:42:45 +03:00
itdoginfo
3b93a914de v0.3.34 2025-04-03 17:27:35 +03:00
itdoginfo
76c5baf1e2 Fix tailscale smartdns in resolve.conf 2025-04-03 17:27:13 +03:00
itdoginfo
c752c46abf Fix resolv_conf value 2025-04-03 17:24:57 +03:00
itdoginfo
1df1defa5e Check curl 2025-04-03 17:24:31 +03:00
itdoginfo
3cb4be6427 v0.3.33 2025-04-03 16:47:47 +03:00
itdoginfo
25bfdce5ce Added critical log. Rm friendlywrt check. Added iptables check 2025-04-03 16:47:26 +03:00
itdoginfo
6d0f097a07 Merge pull request #75 from itdoginfo/feature/error-notification (#47)
Feature/error notification
2025-04-03 14:52:00 +03:00
Ivan K
5f780955eb 💄 style(podkop): update error log filtering criteria 2025-04-03 13:42:55 +03:00
Ivan K
389def9056 ♻️ refactor(podkop): remove unused createErrorModal function 2025-04-03 13:40:41 +03:00
Ivan K
e816da5133 feat(podkop): add error logging and notification system 2025-04-03 13:36:22 +03:00
Ivan K
e57adbe042 🔒 refactor(config): Mask NextDNS server address in config output 2025-03-30 20:36:49 +03:00
itdoginfo
d78c51360d Merge pull request #73 from itdoginfo/feature/no-more-cache
🐛 fix(doh): Improve DoH server compatibility detection for quad9
2025-03-30 18:44:26 +03:00
Ivan K
c2357337fc 🐛 fix(dns): improve DoH server compatibility and error handling 2025-03-30 17:20:06 +03:00
Ivan K
bc6490b56e 🐛 fix(doh): Improve DoH server compatibility detection for quad9 2025-03-30 17:04:30 +03:00
itdoginfo
2f645d9151 v0.3.32 2025-03-30 16:03:49 +03:00
itdoginfo
94cc65001b Merge pull request #72 from itdoginfo/feature/no-more-cache
🐛 fix(podkop): Handle DNS check errors and timeouts properly
2025-03-30 16:02:44 +03:00
Ivan K
87caa70e97 feat(dns): Mask NextDNS ID in DNS availability check output 2025-03-30 14:53:01 +03:00
Ivan K
90d7c60fcb 🐛 fix(podkop): Handle DNS check errors and timeouts properly 2025-03-30 14:46:03 +03:00
itdoginfo
3f114b4710 v0.3.31 2025-03-30 12:41:40 +03:00
itdoginfo
b821abe82c Merge pull request #67 from itdoginfo/feature/no-more-cache
Feature/add DNS and bypass status checks to diagnostics
2025-03-30 12:37:58 +03:00
Ivan K
732cab2ef3 🐛 fix(podkop): fix typo in translation and dns check timeout 2025-03-30 09:54:31 +03:00
Ivan K
3b4ce9e7a3 feat: add visibility change event listener for diagnostics updates 2025-03-21 15:06:08 +03:00
Ivan K
69c4445c85 refactor: move buttons for NFT and DNSMasq checks 2025-03-21 14:54:52 +03:00
Ivan K
dcebc3d67d docs: update Russian translations for proxy configuration and add new translation strings 2025-03-21 14:53:24 +03:00
Ivan K
1be31eaf59 feat: add local DNS availability check and display in UI 2025-03-21 14:47:20 +03:00
Ivan K
023210e0f0 style: update text for Bypass Status to Main config 2025-03-21 14:21:35 +03:00
Ivan K
5ff832533e feat: add DNS and bypass status checks to diagnostics 2025-03-21 13:03:29 +03:00
Ivan K
5d2163515e refactor: improve caching prevention logic 2025-03-20 21:47:55 +03:00
Ivan K
5865706d0c feat: add timestamp to URL to prevent caching 2025-03-20 21:45:08 +03:00
itdoginfo
aabe1c53dc Update 2025-03-18 00:32:06 +03:00
itdoginfo
8e91b582ad #36 2025-03-18 00:31:56 +03:00
itdoginfo
62ce1f5acc v0.3.30 2025-03-17 14:44:07 +03:00
itdoginfo
93727ddeb5 Processing empty values 2025-03-17 14:43:33 +03:00
itdoginfo
98797d93b1 v0.3.29 2025-03-17 13:16:38 +03:00
itdoginfo
66c6e998a2 #38 #46 2025-03-17 13:14:37 +03:00
itdoginfo
3d9f82b571 Merge pull request #65 from itdoginfo/chore/fakeip-method
feat: add diagnostics functionality only in tab
2025-03-14 12:33:52 +03:00
Ivan K
38d082e236 feat: add diagnostics functionality only in tab 2025-03-14 09:50:28 +03:00
itdoginfo
9f5abcae6d v0.3.28 2025-03-13 19:34:52 +03:00
itdoginfo
7836d2c6ec Fix 2025-03-13 19:32:57 +03:00
itdoginfo
f46c934c59 Test 2025-03-13 19:30:17 +03:00
itdoginfo
23ed10d393 Added check version in Makefile 2025-03-13 19:28:31 +03:00
itdoginfo
26488baad3 Merge pull request #64 from itdoginfo/chore/fakeip-method
fix: fix enable/disable functionality to podkop service
2025-03-13 19:05:49 +03:00
Ivan K
c79016e456 feat: add createInitActionButton function to ButtonFactory 2025-03-13 10:35:48 +03:00
Ivan K
884bbfee42 fix: remove unused button creation code 2025-03-13 10:32:34 +03:00
Ivan K
1263b9b1b8 fix: fix enable/disable functionality to podkop service 2025-03-13 10:00:37 +03:00
Ivan K
23203fd7a1 feat: add createSystemButton function to ButtonFactory and flush cache button 2025-03-13 00:10:18 +03:00
itdoginfo
25c887a952 v0.3.27 2025-03-12 17:20:35 +03:00
itdoginfo
e7a3c7adf1 Merge pull request #63 from itdoginfo/chore/fakeip-method
feat: update DNS checks and improve FakeIP status reporting
2025-03-12 17:02:18 +03:00
Ivan K
3e96b9a1af feat: update DNS checks and improve FakeIP status reporting 2025-03-12 16:20:59 +03:00
11 changed files with 943 additions and 231 deletions

View File

@@ -11,6 +11,22 @@ jobs:
steps: steps:
- uses: actions/checkout@v4.2.1 - uses: actions/checkout@v4.2.1
- name: Check version match
run: |
PODKOP_VERSION=$(grep '^PKG_VERSION:=' podkop/Makefile | cut -d '=' -f 2)
LUCI_APP_PODKOP_VERSION=$(grep '^PKG_VERSION:=' luci-app-podkop/Makefile | cut -d '=' -f 2)
TAG_VERSION=${GITHUB_REF#refs/tags/v}
echo "Podkop version: $PODKOP_VERSION"
echo "Luci-app-podkop version: $LUCI_APP_PODKOP_VERSION"
echo "Tag version: $TAG_VERSION"
if [ "$PODKOP_VERSION" != "$TAG_VERSION" ] || [ "$LUCI_APP_PODKOP_VERSION" != "$TAG_VERSION" ]; then
echo "Error: Version mismatch"
exit 1
fi
- name: Build and push - name: Build and push
uses: docker/build-push-action@v6.9.0 uses: docker/build-push-action@v6.9.0
with: with:

View File

@@ -80,12 +80,15 @@ Luci: Services/podkop
# ToDo # ToDo
Этот раздел не означает задачи, которые нужно брать и делать. Это общий список хотелок. Если вы хотите помочь, пожалуйста, спросите сначала в телеграмме. Этот раздел не означает задачи, которые нужно брать и делать. Это общий список хотелок. Если вы хотите помочь, пожалуйста, спросите сначала в телеграмме.
- [ ] Проверка, что версия в makefile совпадает с тегом - [ ] Interface trigger
- [ ] Сделать галку запрещающую подкопу редачить dhcp. Допилить в исключение вместе с пустыми полями proxy и vpn - [ ] Управление sing-box с помощью podkop. sing-box disable
- [x] Обработка ошибки `sing-box[9345]: FATAL[0000] start service: initialize DNS rule[2]: rule-set not found: main`. Когда не задана строка\интерфейс - [ ] Сделать галку запрещающую подкопу редачить dhcp. Допилить в исключение вместе с пустыми полями proxy и vpn (нужно wiki)
- [x] Проверка `/etc/resolv.conf` на наличие DNS-серверов
- [x] Отслеживание интерфейса wan в sing-box
- [ ] Рестарт сервиса без рестарта dnsmasq - [ ] Рестарт сервиса без рестарта dnsmasq
- [ ] `ash: can't kill pid 9848: No such process` при обновлении
- [ ] Luci: Добавить валидацию "Proxy Configuration URL". Если пустое, то ошибка. Как с интерфейсом.
- [ ] После выключения и включения может быть: `Dnsmasq save config error: server=127.0.0.42`
- [ ] Не грузится диагностика полностью при одной нерабочей комманде. Подумать как это можно дебажить легко. https://t.me/itdogchat/142500/378956
- [ ] DoH возможность добавлять сервера c path. Взять пример из NextDNS
Низкий приоритет Низкий приоритет
- [ ] Галочка, которая режет доступ к doh серверам - [ ] Галочка, которая режет доступ к doh серверам
@@ -97,6 +100,17 @@ Luci: Services/podkop
- [ ] Unit тесты (BATS) - [ ] Unit тесты (BATS)
- [ ] Интеграционые тесты бекенда (OpenWrt rootfs + BATS) - [ ] Интеграционые тесты бекенда (OpenWrt rootfs + BATS)
# Don't touch my dhcp
Нужно в первую очередь, чтоб использовать опцию `server`.
В случае если опция активна, podkop не трогает /etc/config/dhcp. И вам требуется самостоятельно указать следующие значения:
```
option noresolv '1'
option cachesize '0'
list server '127.0.0.42'
```
Без этого podkop работать не будет.
# Разработка # Разработка
Есть два варианта: Есть два варианта:
- Просто поставить пакет на роутер или виртуалку и прям редактировать через SFTP (opkg install openssh-sftp-server) - Просто поставить пакет на роутер или виртуалку и прям редактировать через SFTP (opkg install openssh-sftp-server)

View File

@@ -42,6 +42,15 @@ main() {
echo "Installed podkop..." echo "Installed podkop..."
add_tunnel add_tunnel
fi fi
if command -v curl &> /dev/null; then
check_response=$(curl -s "https://api.github.com/repos/itdoginfo/podkop/releases/latest")
if echo "$check_response" | grep -q 'API rate limit '; then
echo "You've reached rate limit from GitHub. Repeat in five minutes."
exit 1
fi
fi
download_success=0 download_success=0
while read -r url; do while read -r url; do
@@ -151,13 +160,13 @@ add_tunnel() {
;; ;;
3) 3)
opkg install opkg install openvpn-openssl luci-app-openvpn opkg install openvpn-openssl luci-app-openvpn
printf "\e[1;32mUse these instructions to configure https://itdog.info/nastrojka-klienta-openvpn-na-openwrt/\e[0m\n" printf "\e[1;32mUse these instructions to configure https://itdog.info/nastrojka-klienta-openvpn-na-openwrt/\e[0m\n"
break break
;; ;;
4) 4)
opkg install opkg install openconnect luci-proto-openconnect opkg install openconnect luci-proto-openconnect
printf "\e[1;32mUse these instructions to configure https://itdog.info/nastrojka-klienta-openconnect-na-openwrt/\e[0m\n" printf "\e[1;32mUse these instructions to configure https://itdog.info/nastrojka-klienta-openconnect-na-openwrt/\e[0m\n"
break break
;; ;;
@@ -239,8 +248,8 @@ install_awg_packages() {
fi fi
fi fi
if opkg list-installed | grep -q luci-app-amneziawg; then if opkg list-installed | grep -qE 'luci-app-amneziawg|luci-proto-amneziawg'; then
echo "luci-app-amneziawg already installed" echo "luci-app-amneziawg or luci-proto-amneziawg already installed"
else else
LUCI_APP_AMNEZIAWG_FILENAME="luci-app-amneziawg${PKGPOSTFIX}" LUCI_APP_AMNEZIAWG_FILENAME="luci-app-amneziawg${PKGPOSTFIX}"
DOWNLOAD_URL="${BASE_URL}v${VERSION}/${LUCI_APP_AMNEZIAWG_FILENAME}" DOWNLOAD_URL="${BASE_URL}v${VERSION}/${LUCI_APP_AMNEZIAWG_FILENAME}"

View File

@@ -1,7 +1,7 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-podkop PKG_NAME:=luci-app-podkop
PKG_VERSION:=0.3.26 PKG_VERSION:=0.3.36
PKG_RELEASE:=1 PKG_RELEASE:=1
LUCI_TITLE:=LuCI podkop app LUCI_TITLE:=LuCI podkop app

View File

@@ -4,6 +4,7 @@
'require ui'; 'require ui';
'require network'; 'require network';
'require fs'; 'require fs';
'require uci';
const STATUS_COLORS = { const STATUS_COLORS = {
SUCCESS: '#4caf50', SUCCESS: '#4caf50',
@@ -11,6 +12,8 @@ const STATUS_COLORS = {
WARNING: '#ff9800' WARNING: '#ff9800'
}; };
const ERROR_POLL_INTERVAL = 5000; // 5 seconds
async function safeExec(command, args = [], timeout = 7000) { async function safeExec(command, args = [], timeout = 7000) {
try { try {
const controller = new AbortController(); const controller = new AbortController();
@@ -41,18 +44,15 @@ function formatDiagnosticOutput(output) {
.replace(/\r/g, '\n'); .replace(/\r/g, '\n');
} }
function getNetworkInterfaces(o, section_id) { function getNetworkInterfaces(o, section_id, excludeInterfaces = []) {
const excludeInterfaces = ['br-lan', 'eth0', 'eth1', 'wan', 'phy0-ap0', 'phy1-ap0', 'pppoe-wan'];
return network.getDevices().then(devices => { return network.getDevices().then(devices => {
// Reset the options by creating a new keylist
o.keylist = []; o.keylist = [];
o.vallist = []; o.vallist = [];
devices.forEach(device => { devices.forEach(device => {
if (device.dev && device.dev.name) { if (device.dev && device.dev.name) {
const deviceName = device.dev.name; const deviceName = device.dev.name;
if (!excludeInterfaces.includes(deviceName) && !/^lan\d+$/.test(deviceName)) { if (!excludeInterfaces.includes(deviceName)) {
o.value(deviceName, deviceName); o.value(deviceName, deviceName);
} }
} }
@@ -62,6 +62,23 @@ function getNetworkInterfaces(o, section_id) {
}); });
} }
function getNetworkNetworks(o, section_id, excludeInterfaces = []) {
return network.getNetworks().then(networks => {
o.keylist = [];
o.vallist = [];
networks.forEach(net => {
const name = net.getName();
const ifname = net.getIfname();
if (name && !excludeInterfaces.includes(name)) {
o.value(name, ifname ? `${name} (${ifname})` : name);
}
});
}).catch(error => {
console.error('Failed to get networks:', error);
});
}
function createConfigSection(section, map, network) { function createConfigSection(section, map, network) {
const s = section; const s = section;
@@ -93,20 +110,29 @@ function createConfigSection(section, map, network) {
if (cfgvalue) { if (cfgvalue) {
try { try {
// Extract only the active configuration (first non-comment line)
const activeConfig = cfgvalue.split('\n') const activeConfig = cfgvalue.split('\n')
.map(line => line.trim()) .map(line => line.trim())
.find(line => line && !line.startsWith('//')); .find(line => line && !line.startsWith('//'));
if (activeConfig) { if (activeConfig) {
const label = activeConfig.split('#').pop() || 'unnamed'; if (activeConfig.includes('#')) {
const decodedLabel = decodeURIComponent(label); const label = activeConfig.split('#').pop();
const descDiv = E('div', { 'class': 'cbi-value-description' }, _('Current config: ') + decodedLabel); if (label && label.trim()) {
container.appendChild(descDiv); const decodedLabel = decodeURIComponent(label);
const descDiv = E('div', { 'class': 'cbi-value-description' }, _('Current config: ') + decodedLabel);
container.appendChild(descDiv);
} else {
const descDiv = E('div', { 'class': 'cbi-value-description' }, _('Config without description'));
container.appendChild(descDiv);
}
} else {
const descDiv = E('div', { 'class': 'cbi-value-description' }, _('Config without description'));
container.appendChild(descDiv);
}
} }
} catch (e) { } catch (e) {
console.error('Error parsing config label:', e); console.error('Error parsing config label:', e);
const descDiv = E('div', { 'class': 'cbi-value-description' }, _('Current config: ') + (cfgvalue.split('#').pop() || 'unnamed')); const descDiv = E('div', { 'class': 'cbi-value-description' }, _('Config without description'));
container.appendChild(descDiv); container.appendChild(descDiv);
} }
} else { } else {
@@ -124,7 +150,6 @@ function createConfigSection(section, map, network) {
} }
try { try {
// Get the first non-comment line as the active configuration
const activeConfig = value.split('\n') const activeConfig = value.split('\n')
.map(line => line.trim()) .map(line => line.trim())
.find(line => line && !line.startsWith('//')); .find(line => line && !line.startsWith('//'));
@@ -243,11 +268,17 @@ function createConfigSection(section, map, network) {
} }
}; };
o = s.taboption('basic', form.Flag, 'ss_uot', _('Shadowsocks UDP over TCP'), _('Apply for SS2022'));
o.default = '0';
o.depends('mode', 'proxy');
o.rmempty = false;
o.ucisection = 'main';
o = s.taboption('basic', form.ListValue, 'interface', _('Network Interface'), _('Select network interface for VPN connection')); o = s.taboption('basic', form.ListValue, 'interface', _('Network Interface'), _('Select network interface for VPN connection'));
o.depends('mode', 'vpn'); o.depends('mode', 'vpn');
o.ucisection = s.section; o.ucisection = s.section;
o.load = function (section_id) { o.load = function (section_id) {
return getNetworkInterfaces(this, section_id).then(() => { return getNetworkInterfaces(this, section_id, ['br-lan', 'eth0', 'eth1', 'wan', 'phy0-ap0', 'phy1-ap0', 'pppoe-wan']).then(() => {
return this.super('load', section_id); return this.super('load', section_id);
}); });
}; };
@@ -614,6 +645,16 @@ const ButtonFactory = {
}); });
}, },
createInitActionButton: function (config) {
return this.createButton({
label: config.label,
additionalClass: `cbi-button-${config.type || ''}`,
onClick: () => safeExec('/etc/init.d/podkop', [config.action])
.then(() => config.reload && location.reload()),
style: config.style
});
},
createModalButton: function (config) { createModalButton: function (config) {
return this.createButton({ return this.createButton({
label: config.label, label: config.label,
@@ -650,32 +691,24 @@ const createStatusPanel = (title, status, buttons) => {
}; };
// Update the status section creation // Update the status section creation
let createStatusSection = function (podkopStatus, singboxStatus, podkop, luci, singbox, system, fakeipStatus, fakeipCLIStatus) { let createStatusSection = function (podkopStatus, singboxStatus, podkop, luci, singbox, system, fakeipStatus, fakeipCLIStatus, dnsStatus, bypassStatus, configName) {
return E('div', { 'class': 'cbi-section' }, [ return E('div', { 'class': 'cbi-section' }, [
E('h3', {}, _('Service Status')),
E('div', { 'class': 'table', style: 'display: flex; gap: 20px;' }, [ E('div', { 'class': 'table', style: 'display: flex; gap: 20px;' }, [
// Podkop Status Panel // Podkop Status Panel
createStatusPanel('Podkop Status', podkopStatus, [ createStatusPanel('Podkop Status', podkopStatus, [
podkopStatus.running ? ButtonFactory.createActionButton({
ButtonFactory.createActionButton({ label: podkopStatus.running ? 'Stop Podkop' : 'Start Podkop',
label: 'Stop Podkop', type: podkopStatus.running ? 'remove' : 'apply',
type: 'remove', action: podkopStatus.running ? 'stop' : 'start',
action: 'stop', reload: true
reload: true }),
}) :
ButtonFactory.createActionButton({
label: 'Start Podkop',
type: 'apply',
action: 'start',
reload: true
}),
ButtonFactory.createActionButton({ ButtonFactory.createActionButton({
label: 'Restart Podkop', label: 'Restart Podkop',
type: 'apply', type: 'apply',
action: 'restart', action: 'restart',
reload: true reload: true
}), }),
ButtonFactory.createActionButton({ ButtonFactory.createInitActionButton({
label: podkopStatus.enabled ? 'Disable Podkop' : 'Enable Podkop', label: podkopStatus.enabled ? 'Disable Podkop' : 'Enable Podkop',
type: podkopStatus.enabled ? 'remove' : 'apply', type: podkopStatus.enabled ? 'remove' : 'apply',
action: podkopStatus.enabled ? 'disable' : 'enable', action: podkopStatus.enabled ? 'disable' : 'enable',
@@ -690,6 +723,11 @@ let createStatusSection = function (podkopStatus, singboxStatus, podkop, luci, s
label: 'View Logs', label: 'View Logs',
command: 'check_logs', command: 'check_logs',
title: 'Podkop Logs' title: 'Podkop Logs'
}),
ButtonFactory.createModalButton({
label: _('Update Lists'),
command: 'list_update',
title: _('Lists Update Results')
}) })
]), ]),
@@ -709,6 +747,16 @@ let createStatusSection = function (podkopStatus, singboxStatus, podkop, luci, s
label: 'Check Connections', label: 'Check Connections',
command: 'check_sing_box_connections', command: 'check_sing_box_connections',
title: 'Active Connections' title: 'Active Connections'
}),
ButtonFactory.createModalButton({
label: _('Check NFT Rules'),
command: 'check_nft',
title: _('NFT Rules')
}),
ButtonFactory.createModalButton({
label: _('Check DNSMasq'),
command: 'check_dnsmasq',
title: _('DNSMasq Configuration')
}) })
]), ]),
@@ -730,26 +778,34 @@ let createStatusSection = function (podkopStatus, singboxStatus, podkop, luci, s
]) ])
]) ])
]), ]),
ButtonFactory.createModalButton({ E('div', { style: 'margin-bottom: 10px;' }, [
label: _('Check NFT Rules'), E('div', { style: 'margin-bottom: 5px;' }, [
command: 'check_nft', E('strong', {}, _('DNS Status')),
title: _('NFT Rules') E('br'),
}), E('span', { style: `color: ${dnsStatus.remote.color}` }, [
ButtonFactory.createModalButton({ dnsStatus.remote.state === 'available' ? '✔' : dnsStatus.remote.state === 'unavailable' ? '✘' : '!',
label: _('Check DNSMasq'), ' ',
command: 'check_dnsmasq', dnsStatus.remote.message
title: _('DNSMasq Configuration') ]),
}), E('br'),
ButtonFactory.createModalButton({ E('span', { style: `color: ${dnsStatus.local.color}` }, [
label: _('Update Lists'), dnsStatus.local.state === 'available' ? '✔' : dnsStatus.local.state === 'unavailable' ? '✘' : '!',
command: 'list_update', ' ',
title: _('Lists Update Results') dnsStatus.local.message
}), ])
ButtonFactory.createModalButton({ ])
label: _('Check Router FakeIP'), ]),
command: 'check_fakeip', E('div', { style: 'margin-bottom: 10px;' }, [
title: _('FakeIP Router Check') E('div', { style: 'margin-bottom: 5px;' }, [
}) E('strong', {}, configName),
E('br'),
E('span', { style: `color: ${bypassStatus.color}` }, [
bypassStatus.state === 'working' ? '✔' : bypassStatus.state === 'not_working' ? '✘' : '!',
' ',
bypassStatus.message
])
])
])
]), ]),
// Version Information Panel // Version Information Panel
@@ -766,12 +822,134 @@ let createStatusSection = function (podkopStatus, singboxStatus, podkop, luci, s
]); ]);
}; };
function checkDNSAvailability() {
const createStatus = (state, message, color) => ({
state,
message: _(message),
color: STATUS_COLORS[color]
});
return new Promise(async (resolve) => {
try {
const dnsStatusResult = await safeExec('/usr/bin/podkop', ['check_dns_available']);
if (!dnsStatusResult || !dnsStatusResult.stdout) {
return resolve({
remote: createStatus('error', 'DNS check timeout', 'WARNING'),
local: createStatus('error', 'DNS check timeout', 'WARNING')
});
}
try {
const dnsStatus = JSON.parse(dnsStatusResult.stdout);
const remoteStatus = dnsStatus.is_available ?
createStatus('available', `${dnsStatus.dns_type.toUpperCase()} (${dnsStatus.dns_server}) available`, 'SUCCESS') :
createStatus('unavailable', `${dnsStatus.dns_type.toUpperCase()} (${dnsStatus.dns_server}) unavailable`, 'ERROR');
const localStatus = dnsStatus.local_dns_working ?
createStatus('available', 'Router DNS working', 'SUCCESS') :
createStatus('unavailable', 'Router DNS not working', 'ERROR');
return resolve({
remote: remoteStatus,
local: localStatus
});
} catch (parseError) {
return resolve({
remote: createStatus('error', 'DNS check parse error', 'WARNING'),
local: createStatus('error', 'DNS check parse error', 'WARNING')
});
}
} catch (error) {
return resolve({
remote: createStatus('error', 'DNS check error', 'WARNING'),
local: createStatus('error', 'DNS check error', 'WARNING')
});
}
});
}
async function getPodkopErrors() {
try {
const result = await safeExec('/usr/bin/podkop', ['check_logs']);
if (!result || !result.stdout) return [];
const logs = result.stdout.split('\n');
const errors = logs.filter(log =>
// log.includes('saved for future filters') ||
log.includes('[critical]')
);
console.log('Found errors:', errors);
return errors;
} catch (error) {
console.error('Error getting podkop logs:', error);
return [];
}
}
let errorPollTimer = null;
let lastErrorsSet = new Set();
let isInitialCheck = true;
function showErrorNotification(error, isMultiple = false) {
const notificationContent = E('div', { 'class': 'alert-message error' }, [
E('pre', { 'class': 'error-log' }, error)
]);
ui.addNotification(null, notificationContent);
}
function startErrorPolling() {
if (errorPollTimer) {
clearInterval(errorPollTimer);
}
async function checkErrors() {
const result = await safeExec('/usr/bin/podkop', ['check_logs']);
if (!result || !result.stdout) return;
const logs = result.stdout;
const errorLines = logs.split('\n').filter(line =>
// line.includes('saved for future filters') ||
line.includes('[critical]')
);
if (errorLines.length > 0) {
const currentErrors = new Set(errorLines);
if (isInitialCheck) {
if (errorLines.length > 0) {
showErrorNotification(errorLines.join('\n'), true);
}
isInitialCheck = false;
} else {
const newErrors = [...currentErrors].filter(error => !lastErrorsSet.has(error));
newErrors.forEach(error => {
showErrorNotification(error, false);
});
}
lastErrorsSet = currentErrors;
}
}
checkErrors();
errorPollTimer = setInterval(checkErrors, ERROR_POLL_INTERVAL);
}
function stopErrorPolling() {
if (errorPollTimer) {
clearInterval(errorPollTimer);
errorPollTimer = null;
}
}
return view.extend({ return view.extend({
async render() { async render() {
document.head.insertAdjacentHTML('beforeend', ` document.head.insertAdjacentHTML('beforeend', `
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<style> <style>
.cbi-value { .cbi-value {
margin-bottom: 10px !important; margin-bottom: 10px !important;
@@ -917,6 +1095,29 @@ return view.extend({
return true; return true;
}; };
o = mainSection.taboption('additional', form.MultiValue, 'iface', _('Source Network Interface'), _('Select the network interface from which the traffic will originate'));
o.ucisection = 'main';
o.default = 'br-lan';
o.load = function (section_id) {
return getNetworkInterfaces(this, section_id, ['wan', 'phy0-ap0', 'phy1-ap0', 'pppoe-wan']).then(() => {
return this.super('load', section_id);
});
};
o = mainSection.taboption('additional', form.MultiValue, 'restart_ifaces', _('Interface for monitoring'), _('Select the WAN interfaces to be monitored'));
o.ucisection = 'main';
o.default = 'wan';
o.load = function (section_id) {
return getNetworkNetworks(this, section_id, ['lan', 'loopback']).then(() => {
return this.super('load', section_id);
});
};
o = mainSection.taboption('additional', form.Flag, 'dont_touch_dhcp', _('Dont touch my DHCP!'), _('Podkop will not change the DHCP config'));
o.default = '0';
o.rmempty = false;
o.ucisection = 'main';
// Extra IPs and exclusions (main section) // Extra IPs and exclusions (main section)
o = mainSection.taboption('basic', form.Flag, 'exclude_from_ip_enabled', _('IP for exclusion'), _('Specify local IP addresses that will never use the configured route')); o = mainSection.taboption('basic', form.Flag, 'exclude_from_ip_enabled', _('IP for exclusion'), _('Specify local IP addresses that will never use the configured route'));
o.default = '0'; o.default = '0';
@@ -950,7 +1151,39 @@ return view.extend({
o = mainSection.taboption('diagnostics', form.DummyValue, '_status'); o = mainSection.taboption('diagnostics', form.DummyValue, '_status');
o.rawhtml = true; o.rawhtml = true;
o.cfgvalue = () => E('div', { id: 'diagnostics-status' }, _('Loading diagnostics...')); o.cfgvalue = () => E('div', {
id: 'diagnostics-status',
'style': 'cursor: pointer;'
}, _('Click to load diagnostics...'));
let diagnosticsUpdateTimer = null;
function startDiagnosticsUpdates() {
if (diagnosticsUpdateTimer) {
clearInterval(diagnosticsUpdateTimer);
}
const container = document.getElementById('diagnostics-status');
if (container) {
container.innerHTML = _('Loading diagnostics...');
}
updateDiagnostics();
diagnosticsUpdateTimer = setInterval(updateDiagnostics, 10000);
}
function stopDiagnosticsUpdates() {
if (diagnosticsUpdateTimer) {
clearInterval(diagnosticsUpdateTimer);
diagnosticsUpdateTimer = null;
}
// Reset the loading state when stopping updates
const container = document.getElementById('diagnostics-status');
if (container) {
container.removeAttribute('data-loading');
}
}
function checkFakeIP() { function checkFakeIP() {
const createStatus = (state, message, color) => ({ const createStatus = (state, message, color) => ({
@@ -990,7 +1223,6 @@ return view.extend({
return resolve(createStatus('error', message, 'WARNING')); return resolve(createStatus('error', message, 'WARNING'));
} }
} catch (error) { } catch (error) {
console.error('Error in checkFakeIP:', error);
return resolve(createStatus('error', 'check error', 'WARNING')); return resolve(createStatus('error', 'check error', 'WARNING'));
} }
}); });
@@ -1015,20 +1247,95 @@ return view.extend({
return resolve(createStatus('not_working', 'DNS not configured', 'ERROR')); return resolve(createStatus('not_working', 'DNS not configured', 'ERROR'));
} }
const fakeipResult = await safeExec('/usr/bin/podkop', ['check_fakeip']); const result = await safeExec('nslookup', ['-timeout=2', 'fakeip.tech-domain.club', '127.0.0.42']);
if (fakeipResult.stdout && fakeipResult.stdout.includes('198.18')) { if (result.stdout && result.stdout.includes('198.18')) {
return resolve(createStatus('working', 'FakeIP working (CLI)', 'SUCCESS')); return resolve(createStatus('working', 'working on router', 'SUCCESS'));
} else { } else {
return resolve(createStatus('not_working', 'FakeIP not working (CLI)', 'ERROR')); return resolve(createStatus('not_working', 'not working on router', 'ERROR'));
} }
} catch (error) { } catch (error) {
console.error('Error in checkFakeIPCLI:', error);
return resolve(createStatus('error', 'CLI check error', 'WARNING')); return resolve(createStatus('error', 'CLI check error', 'WARNING'));
} }
}); });
} }
function checkBypass() {
const createStatus = (state, message, color) => ({
state,
message: _(message),
color: STATUS_COLORS[color]
});
return new Promise(async (resolve) => {
try {
let configMode = 'proxy'; // Default fallback
try {
const formData = document.querySelector('form.map-podkop');
if (formData) {
const modeSelect = formData.querySelector('select[name="cbid.podkop.main.mode"]');
if (modeSelect && modeSelect.value) {
configMode = modeSelect.value;
}
}
} catch (formError) {
console.error('Error getting mode from form:', formError);
}
// Check if sing-box is running
const singboxStatusResult = await safeExec('/usr/bin/podkop', ['get_sing_box_status']);
const singboxStatus = JSON.parse(singboxStatusResult.stdout || '{"running":0,"dns_configured":0}');
if (!singboxStatus.running) {
return resolve(createStatus('not_working', `${configMode} not running`, 'ERROR'));
}
// Fetch IP from first endpoint
let ip1 = null;
try {
const controller1 = new AbortController();
const timeoutId1 = setTimeout(() => controller1.abort(), 10000);
const response1 = await fetch('https://fakeip.tech-domain.club/check', { signal: controller1.signal });
const data1 = await response1.json();
clearTimeout(timeoutId1);
ip1 = data1.IP;
} catch (error) {
return resolve(createStatus('error', 'First endpoint check failed', 'WARNING'));
}
// Fetch IP from second endpoint
let ip2 = null;
try {
const controller2 = new AbortController();
const timeoutId2 = setTimeout(() => controller2.abort(), 10000);
const response2 = await fetch('https://ip.tech-domain.club/check', { signal: controller2.signal });
const data2 = await response2.json();
clearTimeout(timeoutId2);
ip2 = data2.IP;
} catch (error) {
return resolve(createStatus('not_working', `${configMode} not working`, 'ERROR'));
}
// Compare IPs
if (ip1 && ip2) {
if (ip1 !== ip2) {
return resolve(createStatus('working', `${configMode} working correctly`, 'SUCCESS'));
} else {
return resolve(createStatus('not_working', `${configMode} routing incorrect`, 'ERROR'));
}
} else {
return resolve(createStatus('error', 'IP comparison failed', 'WARNING'));
}
} catch (error) {
return resolve(createStatus('error', 'Bypass check error', 'WARNING'));
}
});
}
async function updateDiagnostics() { async function updateDiagnostics() {
try { try {
const [ const [
@@ -1039,7 +1346,9 @@ return view.extend({
singbox, singbox,
system, system,
fakeipStatus, fakeipStatus,
fakeipCLIStatus fakeipCLIStatus,
dnsStatus,
bypassStatus
] = await Promise.all([ ] = await Promise.all([
safeExec('/usr/bin/podkop', ['get_status']), safeExec('/usr/bin/podkop', ['get_status']),
safeExec('/usr/bin/podkop', ['get_sing_box_status']), safeExec('/usr/bin/podkop', ['get_sing_box_status']),
@@ -1048,7 +1357,9 @@ return view.extend({
safeExec('/usr/bin/podkop', ['show_sing_box_version']), safeExec('/usr/bin/podkop', ['show_sing_box_version']),
safeExec('/usr/bin/podkop', ['show_system_info']), safeExec('/usr/bin/podkop', ['show_system_info']),
checkFakeIP(), checkFakeIP(),
checkFakeIPCLI() checkFakeIPCLI(),
checkDNSAvailability(),
checkBypass()
]); ]);
const parsedPodkopStatus = JSON.parse(podkopStatus.stdout || '{"running":0,"enabled":0,"status":"unknown"}'); const parsedPodkopStatus = JSON.parse(podkopStatus.stdout || '{"running":0,"enabled":0,"status":"unknown"}');
@@ -1057,7 +1368,35 @@ return view.extend({
const container = document.getElementById('diagnostics-status'); const container = document.getElementById('diagnostics-status');
if (!container) return; if (!container) return;
const statusSection = createStatusSection(parsedPodkopStatus, parsedSingboxStatus, podkop, luci, singbox, system, fakeipStatus, fakeipCLIStatus); let configName = _('Main config');
try {
const data = await uci.load('podkop');
const proxyString = uci.get('podkop', 'main', 'proxy_string');
if (proxyString) {
const activeConfig = proxyString.split('\n')
.map(line => line.trim())
.find(line => line && !line.startsWith('//'));
if (activeConfig) {
if (activeConfig.includes('#')) {
const label = activeConfig.split('#').pop();
if (label && label.trim()) {
configName = _('Config: ') + decodeURIComponent(label);
} else {
configName = _('Main config');
}
} else {
configName = _('Main config');
}
}
}
} catch (e) {
console.error('Error getting config name from UCI:', e);
}
// Create a modified statusSection function with the configName
const statusSection = createStatusSection(parsedPodkopStatus, parsedSingboxStatus, podkop, luci, singbox, system, fakeipStatus, fakeipCLIStatus, dnsStatus, bypassStatus, configName);
container.innerHTML = ''; container.innerHTML = '';
container.appendChild(statusSection); container.appendChild(statusSection);
@@ -1076,8 +1415,23 @@ return view.extend({
fakeipCLIStatus.message fakeipCLIStatus.message
]).outerHTML; ]).outerHTML;
} }
const dnsRemoteElement = document.getElementById('dns-remote-status');
if (dnsRemoteElement) {
dnsRemoteElement.innerHTML = E('span', { 'style': `color: ${dnsStatus.remote.color}` }, [
dnsStatus.remote.state === 'available' ? '✔ ' : dnsStatus.remote.state === 'unavailable' ? '✘ ' : '! ',
dnsStatus.remote.message
]).outerHTML;
}
const dnsLocalElement = document.getElementById('dns-local-status');
if (dnsLocalElement) {
dnsLocalElement.innerHTML = E('span', { 'style': `color: ${dnsStatus.local.color}` }, [
dnsStatus.local.state === 'available' ? '✔ ' : dnsStatus.local.state === 'unavailable' ? '✘ ' : '! ',
dnsStatus.local.message
]).outerHTML;
}
} catch (e) { } catch (e) {
console.error('Error updating diagnostics:', e);
const container = document.getElementById('diagnostics-status'); const container = document.getElementById('diagnostics-status');
if (container) { if (container) {
container.innerHTML = E('div', { 'class': 'alert-message warning' }, [ container.innerHTML = E('div', { 'class': 'alert-message warning' }, [
@@ -1089,62 +1443,6 @@ return view.extend({
} }
} }
function startPeriodicUpdates(titleDiv) {
let updateTimer = null;
let isVisible = !document.hidden;
let versionText = _('Podkop');
let versionReceived = false;
const updateStatus = async () => {
try {
if (!versionReceived) {
const version = await safeExec('/usr/bin/podkop', ['show_version'], 2000);
if (version.stdout) {
versionText = _('Podkop') + ' v' + version.stdout.trim();
versionReceived = true;
}
}
const singboxStatusResult = await safeExec('/usr/bin/podkop', ['get_sing_box_status']);
const singboxStatus = JSON.parse(singboxStatusResult.stdout || '{"running":0,"dns_configured":0}');
const fakeipStatus = await checkFakeIP();
const fakeipCLIStatus = await checkFakeIPCLI();
titleDiv.textContent = versionText + (!singboxStatus.running || !singboxStatus.dns_configured === 'not_working' ? ' (not working)' : '');
await updateDiagnostics();
} catch (error) {
console.warn('Failed to update status:', error);
titleDiv.textContent = versionText + ' (not working)';
}
};
const toggleUpdates = (visible) => {
if (visible) {
updateStatus();
if (!updateTimer) {
updateTimer = setInterval(updateStatus, 10000);
}
} else if (updateTimer) {
clearInterval(updateTimer);
updateTimer = null;
}
};
document.addEventListener('visibilitychange', () => {
isVisible = !document.hidden;
toggleUpdates(isVisible);
});
toggleUpdates(isVisible);
window.addEventListener('unload', () => {
if (updateTimer) {
clearInterval(updateTimer);
}
});
}
// Extra Section // Extra Section
const extraSection = m.section(form.TypedSection, 'extra', _('Extra configurations')); const extraSection = m.section(form.TypedSection, 'extra', _('Extra configurations'));
extraSection.anonymous = false; extraSection.anonymous = false;
@@ -1157,8 +1455,62 @@ return view.extend({
const titleDiv = E('h2', { 'class': 'cbi-map-title' }, _('Podkop')); const titleDiv = E('h2', { 'class': 'cbi-map-title' }, _('Podkop'));
node.insertBefore(titleDiv, node.firstChild); node.insertBefore(titleDiv, node.firstChild);
document.addEventListener('visibilitychange', function () {
const diagnosticsContainer = document.getElementById('diagnostics-status');
if (document.hidden) {
stopDiagnosticsUpdates();
stopErrorPolling();
} else if (diagnosticsContainer && diagnosticsContainer.hasAttribute('data-loading')) {
startDiagnosticsUpdates();
startErrorPolling();
}
});
setTimeout(() => {
const diagnosticsContainer = document.getElementById('diagnostics-status');
if (diagnosticsContainer) {
diagnosticsContainer.addEventListener('click', function () {
if (!this.hasAttribute('data-loading')) {
this.setAttribute('data-loading', 'true');
startDiagnosticsUpdates();
startErrorPolling();
}
});
}
const tabs = node.querySelectorAll('.cbi-tabmenu');
if (tabs.length > 0) {
tabs[0].addEventListener('click', function (e) {
const tab = e.target.closest('.cbi-tab');
if (tab) {
const tabName = tab.getAttribute('data-tab');
if (tabName === 'diagnostics') {
const container = document.getElementById('diagnostics-status');
if (container && !container.hasAttribute('data-loading')) {
container.setAttribute('data-loading', 'true');
startDiagnosticsUpdates();
startErrorPolling();
}
} else {
stopDiagnosticsUpdates();
stopErrorPolling();
}
}
});
const activeTab = tabs[0].querySelector('.cbi-tab[data-tab="diagnostics"]');
if (activeTab) {
const container = document.getElementById('diagnostics-status');
if (container && !container.hasAttribute('data-loading')) {
container.setAttribute('data-loading', 'true');
startDiagnosticsUpdates();
startErrorPolling();
}
}
}
}, 100);
node.classList.add('fade-in'); node.classList.add('fade-in');
startPeriodicUpdates(titleDiv);
return node; return node;
}); });

View File

@@ -40,8 +40,8 @@ msgstr "Конфигурация Outbound"
msgid "Proxy Configuration URL" msgid "Proxy Configuration URL"
msgstr "URL конфигурации прокси" msgstr "URL конфигурации прокси"
msgid "Enter connection string starting with vless:// or ss:// for proxy configuration" msgid "Enter connection string starting with vless:// or ss:// for proxy configuration. Add comments with // for saving other configs"
msgstr "Введите строку подключения, начинающуюся с vless:// или ss:// для настройки прокси" msgstr "Введите строку подключения, начинающуюся с vless:// или ss:// для настройки прокси. Добавляйте комментарии с // для сохранения других конфигураций"
msgid "Outbound Configuration" msgid "Outbound Configuration"
msgstr "Конфигурация исходящего соединения" msgstr "Конфигурация исходящего соединения"
@@ -88,8 +88,8 @@ msgstr "Введите имена доменов без протоколов (п
msgid "User Domains List" msgid "User Domains List"
msgstr "Список пользовательских доменов" msgstr "Список пользовательских доменов"
msgid "Enter domain names separated by comma, space or newline (example: sub.example.com, example.com or one domain per line)" msgid "Enter domain names separated by comma, space or newline. You can add comments after //"
msgstr "Введите имена доменов через запятую, пробел или новую строку (пример: sub.example.com, example.com или один домен на строку)" msgstr "Введите имена доменов, разделяя их запятой, пробелом или с новой строки. Вы можете добавлять комментарии после //"
msgid "Local Domain Lists" msgid "Local Domain Lists"
msgstr "Локальные списки доменов" msgstr "Локальные списки доменов"
@@ -556,6 +556,9 @@ msgstr "Путь должен содержать хотя бы одну дире
msgid "Invalid path format. Must be like /tmp/cache.db" msgid "Invalid path format. Must be like /tmp/cache.db"
msgstr "Неверный формат пути. Пример: /tmp/cache.db" msgstr "Неверный формат пути. Пример: /tmp/cache.db"
msgid "Select the network interface from which the traffic will originate"
msgstr "Выберите сетевой интерфейс, с которого будет исходить трафик"
msgid "Copy to Clipboard" msgid "Copy to Clipboard"
msgstr "Копировать в буфер обмена" msgstr "Копировать в буфер обмена"
@@ -746,4 +749,73 @@ msgid "not works in browser"
msgstr "не работает в браузере" msgstr "не работает в браузере"
msgid "not works on router" msgid "not works on router"
msgstr "не работает на роутере" msgstr "не работает на роутере"
msgid "Diagnostics"
msgstr "Диагностика"
msgid "DNS Status"
msgstr "Статус DNS"
msgid "Bypass Status"
msgstr "Статус обхода"
msgid "proxy working correctly"
msgstr "прокси работает корректно"
msgid "vpn working correctly"
msgstr "vpn работает корректно"
msgid "proxy not working"
msgstr "прокси не работает"
msgid "vpn not working"
msgstr "vpn не работает"
msgid "proxy not running"
msgstr "прокси не запущен"
msgid "vpn not running"
msgstr "vpn не запущен"
msgid "proxy routing incorrect"
msgstr "маршрутизация прокси некорректна"
msgid "vpn routing incorrect"
msgstr "маршрутизация vpn некорректна"
msgid "First endpoint check failed"
msgstr "Проверка первой конечной точки не удалась"
msgid "IP comparison failed"
msgstr "Сравнение IP-адресов не удалось"
msgid "Bypass check error"
msgstr "Ошибка проверки обхода"
msgid "Main config"
msgstr "Основная конфигурация"
msgid "Config without description"
msgstr "Конфигурация без описания"
msgid "DNS working"
msgstr "DNS работает"
msgid "Router DNS working"
msgstr "DNS роутера работает"
msgid "Router DNS not working"
msgstr "DNS роутера не работает"
msgid "DNS check error"
msgstr "Ошибка проверки DNS"
msgid "available"
msgstr "доступен"
msgid "unavailable"
msgstr "недоступен"
msgid "Apply for SS2022"
msgstr "Применить для SS2022"

View File

@@ -1100,4 +1100,73 @@ msgid "not works in browser"
msgstr "" msgstr ""
msgid "not works on router" msgid "not works on router"
msgstr ""
msgid "Diagnostics"
msgstr ""
msgid "DNS Status"
msgstr ""
msgid "Bypass Status"
msgstr ""
msgid "proxy working correctly"
msgstr ""
msgid "vpn working correctly"
msgstr ""
msgid "proxy not working"
msgstr ""
msgid "vpn not working"
msgstr ""
msgid "proxy not running"
msgstr ""
msgid "vpn not running"
msgstr ""
msgid "proxy routing incorrect"
msgstr ""
msgid "vpn routing incorrect"
msgstr ""
msgid "First endpoint check failed"
msgstr ""
msgid "IP comparison failed"
msgstr ""
msgid "Bypass check error"
msgstr ""
msgid "Main config"
msgstr ""
msgid "Enter connection string starting with vless:// or ss:// for proxy configuration. Add comments with // for backup configs"
msgstr ""
msgid "Config without description"
msgstr ""
msgid "DNS working"
msgstr ""
msgid "Router DNS working"
msgstr ""
msgid "Router DNS not working"
msgstr ""
msgid "DNS check error"
msgstr ""
msgid "available"
msgstr ""
msgid "unavailable"
msgstr "" msgstr ""

View File

@@ -1,7 +1,7 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=podkop PKG_NAME:=podkop
PKG_VERSION:=0.3.26 PKG_VERSION:=0.3.36
PKG_RELEASE:=1 PKG_RELEASE:=1
PKG_MAINTAINER:=ITDog <podkop@itdog.info> PKG_MAINTAINER:=ITDog <podkop@itdog.info>

View File

@@ -34,4 +34,7 @@ config main 'main'
option dns_type 'doh' option dns_type 'doh'
option dns_server '8.8.8.8' option dns_server '8.8.8.8'
option dns_rewrite_ttl '60' option dns_rewrite_ttl '60'
option cache_file '/tmp/cache.db' option cache_file '/tmp/cache.db'
list iface 'br-lan'
list restart_ifaces 'wan'
option ss_uot '0'

View File

@@ -6,38 +6,15 @@ USE_PROCD=1
script=$(readlink "$initscript") script=$(readlink "$initscript")
NAME="$(basename ${script:-$initscript})" NAME="$(basename ${script:-$initscript})"
config_load "$NAME" config_load "$NAME"
resolv_conf="/etc/resolv.conf"
start_service() { start_service() {
echo "Start podkop" echo "Start podkop"
sing_box_version=$(sing-box version | head -n 1 | awk '{print $3}') config_get restart_ifaces "main" "restart_ifaces"
required_version="1.11.1"
if [ "$(echo -e "$sing_box_version\n$required_version" | sort -V | head -n 1)" != "$required_version" ]; then
echo "The version of sing-box ($sing_box_version) is lower than the minimum version. Update sing-box: opkg update && opkg remove sing-box && opkg install sing-box"
exit 1
fi
if grep -q FriendlyWrt /etc/banner; then
printf "\033[31;1mYou use FriendlyWrt. If you have problems, check out: https://t.me/itdogchat/44512/181082\033[0m\n"
fi
if grep -qE 'doh_backup_noresolv|doh_backup_server|doh_server' /etc/config/dhcp; then
printf "\033[31;1mDetected https-dns-proxy. Disable or uninstall it for correct functionality.\033[0m\n"
fi
if ! ip addr | grep -q "br-lan"; then
echo "Interface br-lan not found"
exit 1
fi
if ! grep -q "search lan" "$resolv_conf" || ! grep -q "nameserver 127.0.0.1" "$resolv_conf"; then
echo "/etc/resolv.conf does not contain 'search lan' or 'nameserver 127.0.0.1' entries"
fi
procd_open_instance procd_open_instance
procd_set_param command /bin/sh -c "/usr/bin/podkop start" procd_set_param command /usr/bin/podkop start
[ -z "$restart_ifaces" ] || procd_set_param netdev $restart_ifaces
procd_set_param stdout 1 procd_set_param stdout 1
procd_set_param stderr 1 procd_set_param stderr 1
procd_close_instance procd_close_instance
@@ -47,17 +24,19 @@ stop_service() {
/usr/bin/podkop stop /usr/bin/podkop stop
} }
restart_service() {
stop
start
}
reload_service() { reload_service() {
stop /usr/bin/podkop reload > /dev/null 2>&1
start
} }
service_triggers() { service_triggers() {
echo "service_triggers start" echo "service_triggers start"
procd_add_config_trigger "config.change" "$NAME" "$initscript" reload 'on_config_change'
config_get restart_ifaces "main" "restart_ifaces"
procd_open_trigger
procd_add_config_trigger "config.change" "$NAME" "$initscript" reload 'on_config_change'
for iface in $restart_ifaces; do
procd_add_reload_interface_trigger $iface
done
procd_close_trigger
} }

View File

@@ -19,7 +19,10 @@ SING_BOX_CONFIG="/etc/sing-box/config.json"
FAKEIP="198.18.0.0/15" FAKEIP="198.18.0.0/15"
VALID_SERVICES="russia_inside russia_outside ukraine_inside geoblock block porn news anime youtube discord meta twitter hdrezka tiktok telegram" VALID_SERVICES="russia_inside russia_outside ukraine_inside geoblock block porn news anime youtube discord meta twitter hdrezka tiktok telegram"
DNS_RESOLVERS="1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 9.9.9.9 9.9.9.11 94.140.14.14 94.140.15.15 208.67.220.220 208.67.222.222 77.88.8.1 77.88.8.8" DNS_RESOLVERS="1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 9.9.9.9 9.9.9.11 94.140.14.14 94.140.15.15 208.67.220.220 208.67.222.222 77.88.8.1 77.88.8.8"
TEST_DOMAIN="google.com" TEST_DOMAIN="fakeip.tech-domain.club"
INTERFACES_LIST=""
SRC_INTERFACE=""
RESOLV_CONF="/etc/resolv.conf"
log() { log() {
local message="$1" local message="$1"
@@ -42,7 +45,30 @@ nolog() {
echo -e "${CYAN}[$timestamp]${RESET} ${GREEN}$message${RESET}" echo -e "${CYAN}[$timestamp]${RESET} ${GREEN}$message${RESET}"
} }
start() { start_main() {
log "Starting podkop"
# checking
sing_box_version=$(sing-box version | head -n 1 | awk '{print $3}')
required_version="1.11.1"
if [ "$(echo -e "$sing_box_version\n$required_version" | sort -V | head -n 1)" != "$required_version" ]; then
log "[critical] The version of sing-box ($sing_box_version) is lower than the minimum version. Update sing-box: opkg update && opkg remove sing-box && opkg install sing-box"
exit 1
fi
if opkg list-installed | grep -q iptables-mod-extra; then
log "[critical] Conflicting package detected: iptables-mod-extra"
fi
if grep -qE 'doh_backup_noresolv|doh_backup_server|doh_server' /etc/config/dhcp; then
log "[critical] Detected https-dns-proxy. Disable or uninstall it for correct functionality."
fi
if grep -E "^nameserver\s+([0-9]{1,3}\.){3}[0-9]{1,3}" "$RESOLV_CONF" | grep -vqE "127\.0\.0\.1|0\.0\.0\.0"; then
log "[critical] /etc/resolv.conf contains an external nameserver"
fi
migration migration
config_foreach process_validate_service config_foreach process_validate_service
@@ -50,7 +76,7 @@ start() {
sleep 3 sleep 3
mkdir -p /tmp/podkop mkdir -p /tmp/podkop
# base # base
route_table_rule_mark route_table_rule_mark
create_nft_table create_nft_table
@@ -61,6 +87,7 @@ start() {
sing_box_dns sing_box_dns
sing_box_dns_rule_fakeip sing_box_dns_rule_fakeip
sing_box_rule_dns sing_box_rule_dns
sing_box_create_bypass_ruleset
sing_box_add_secure_dns_probe_domain sing_box_add_secure_dns_probe_domain
sing_box_cache_file sing_box_cache_file
process_socks5 process_socks5
@@ -108,8 +135,13 @@ start() {
fi fi
sing_box_config_check sing_box_config_check
/etc/init.d/sing-box restart /etc/init.d/sing-box start
/etc/init.d/sing-box enable #/etc/init.d/sing-box enable
log "Nice"
}
start() {
start_main
config_get proxy_string "main" "proxy_string" config_get proxy_string "main" "proxy_string"
config_get interface "main" "interface" config_get interface "main" "interface"
@@ -123,7 +155,7 @@ start() {
fi fi
} }
stop() { stop_main() {
log "Stopping the podkop" log "Stopping the podkop"
if [ -f /var/run/podkop_list_update.pid ]; then if [ -f /var/run/podkop_list_update.pid ]; then
@@ -137,11 +169,6 @@ stop() {
remove_cron_job remove_cron_job
config_get_bool dont_touch_dhcp "main" "dont_touch_dhcp" "0"
if [ "$dont_touch_dhcp" -eq 0 ]; then
dnsmasq_restore
fi
rm -rf /tmp/podkop/*.lst rm -rf /tmp/podkop/*.lst
log "Flush nft" log "Flush nft"
@@ -161,8 +188,22 @@ stop() {
log "Stop sing-box" log "Stop sing-box"
/etc/init.d/sing-box stop /etc/init.d/sing-box stop
/etc/init.d/sing-box disable #/etc/init.d/sing-box disable
}
stop() {
config_get_bool dont_touch_dhcp "main" "dont_touch_dhcp" "0"
if [ "$dont_touch_dhcp" -eq 0 ]; then
dnsmasq_restore
fi
stop_main
}
reload() {
log "Podkop reload"
stop_main
start_main
} }
# Migrations and validation funcs # Migrations and validation funcs
@@ -261,20 +302,54 @@ route_table_rule_mark() {
fi fi
} }
process_interfaces() {
local iface="$1"
INTERFACES_LIST="$INTERFACES_LIST $iface"
iface_flag=1
}
nft_interfaces() {
local table=PodkopTable
iface_flag=0
config_list_foreach "main" "iface" "process_interfaces"
if [ "$iface_flag" -eq 0 ]; then
SRC_INTERFACE="br-lan"
elif [ $(echo "$INTERFACES_LIST" | wc -w) -eq 1 ]; then
SRC_INTERFACE=$INTERFACES_LIST
else
local set_name="interfaces"
if ! nft list set inet $table $set_name &>/dev/null; then
nft add set inet $table $set_name { type ifname\; flags interval\; }
fi
for interface in $INTERFACES_LIST; do
if ! nft list element inet $table $set_name { $interface } &>/dev/null; then
nft add element inet $table $set_name { $interface }
fi
done
SRC_INTERFACE=@$set_name
fi
}
create_nft_table() { create_nft_table() {
local table="PodkopTable" local table="PodkopTable"
nft add table inet $table nft add table inet $table
nft_interfaces
log "Create nft rules" log "Create nft rules"
nft add chain inet $table mangle { type filter hook prerouting priority -150 \; policy accept \;} nft add chain inet $table mangle { type filter hook prerouting priority -150 \; policy accept \;}
nft add chain inet $table proxy { type filter hook prerouting priority -100 \; policy accept \;} nft add chain inet $table proxy { type filter hook prerouting priority -100 \; policy accept \;}
nft add set inet $table podkop_subnets { type ipv4_addr\; flags interval\; auto-merge\; } nft add set inet $table podkop_subnets { type ipv4_addr\; flags interval\; auto-merge\; }
nft add rule inet $table mangle iifname "br-lan" ip daddr @podkop_subnets meta l4proto tcp meta mark set 0x105 counter nft add rule inet $table mangle iifname "$SRC_INTERFACE" ip daddr @podkop_subnets meta l4proto tcp meta mark set 0x105 counter
nft add rule inet $table mangle iifname "br-lan" ip daddr @podkop_subnets meta l4proto udp meta mark set 0x105 counter nft add rule inet $table mangle iifname "$SRC_INTERFACE" ip daddr @podkop_subnets meta l4proto udp meta mark set 0x105 counter
nft add rule inet $table mangle iifname "br-lan" ip daddr "$FAKEIP" meta l4proto tcp meta mark set 0x105 counter nft add rule inet $table mangle iifname "$SRC_INTERFACE" ip daddr "$FAKEIP" meta l4proto tcp meta mark set 0x105 counter
nft add rule inet $table mangle iifname "br-lan" ip daddr "$FAKEIP" meta l4proto udp meta mark set 0x105 counter nft add rule inet $table mangle iifname "$SRC_INTERFACE" ip daddr "$FAKEIP" meta l4proto udp meta mark set 0x105 counter
nft add rule inet $table proxy meta mark 0x105 meta l4proto tcp tproxy ip to :1602 counter nft add rule inet $table proxy meta mark 0x105 meta l4proto tcp tproxy ip to :1602 counter
nft add rule inet $table proxy meta mark 0x105 meta l4proto udp tproxy ip to :1602 counter nft add rule inet $table proxy meta mark 0x105 meta l4proto udp tproxy ip to :1602 counter
@@ -511,12 +586,11 @@ list_update() {
find_working_resolver() { find_working_resolver() {
local resolver_found="" local resolver_found=""
for resolver in $DNS_RESOLVERS; do for resolver in $DNS_RESOLVERS; do
if nslookup $TEST_DOMAIN $resolver >/dev/null 2>&1; then if nslookup -timeout=2 $TEST_DOMAIN $resolver >/dev/null 2>&1; then
echo "$resolver" echo "$resolver"
return 0 return 0
fi fi
done done
echo "8.8.8.8"
return 1 return 1
} }
@@ -532,10 +606,12 @@ sing_box_uci() {
log "Change sing-box UCI config" log "Change sing-box UCI config"
fi fi
if grep -q '#\s*list ifaces' "$config"; then [ -f /etc/rc.d/S99sing-box ] && log "Disable sing-box" && /etc/init.d/sing-box disable
sed -i '/ifaces/s/#//g' $config
log "Uncommented list ifaces" # if grep -q '#\s*list ifaces' "$config"; then
fi # sed -i '/ifaces/s/#//g' $config
# log "Uncommented list ifaces"
# fi
} }
add_socks5_for_section() { add_socks5_for_section() {
@@ -627,7 +703,12 @@ sing_box_dns() {
if [ "$is_ip" = "0" ]; then if [ "$is_ip" = "0" ]; then
log "Finding working DNS resolver" log "Finding working DNS resolver"
local dns_resolver=$(find_working_resolver) local dns_resolver=$(find_working_resolver)
log "Found working resolver: $dns_resolver" if [ -z "$dns_resolver" ]; then
log "No working resolver found, using default DNS server"
dns_resolver="1.1.1.1"
else
log "Found working resolver: $dns_resolver"
fi
fi fi
log "Configure DNS in sing-box" log "Configure DNS in sing-box"
@@ -686,6 +767,42 @@ sing_box_dns() {
}' $SING_BOX_CONFIG > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG }' $SING_BOX_CONFIG > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
} }
sing_box_create_bypass_ruleset() {
log "Creating bypass ruleset for direct access"
jq '
.route.rule_set += [{
"tag": "bypass",
"type": "inline",
"rules": [
{
"domain_suffix": [
"ip.tech-domain.club"
]
}
]
}]' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
# Add a rule to route bypass domains to direct-out outbound
jq '
.route.rules += [{
"inbound": ["tproxy-in"],
"rule_set": ["bypass"],
"outbound": "main",
"action": "route"
}]' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
# Make sure the bypass ruleset is in the fakeip DNS rule
jq '
.dns.rules = (.dns.rules | map(
if .server == "fakeip-server" then
.rule_set += ["bypass"]
else
.
end
))' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
}
sing_box_dns_rule_fakeip() { sing_box_dns_rule_fakeip() {
local rewrite_ttl local rewrite_ttl
config_get rewrite_ttl "main" "dns_rewrite_ttl" "600" config_get rewrite_ttl "main" "dns_rewrite_ttl" "600"
@@ -765,7 +882,7 @@ sing_box_outdound() {
config_get interface "$section" "interface" config_get interface "$section" "interface"
if [ -z "$interface" ]; then if [ -z "$interface" ]; then
log "VPN interface is not set. Exit" log "[critical] VPN interface is not set. Exit"
exit 1 exit 1
fi fi
@@ -791,12 +908,13 @@ sing_box_outdound() {
active_proxy_string=$(echo "$proxy_string" | grep -v "^[[:space:]]*\/\/" | head -n 1) active_proxy_string=$(echo "$proxy_string" | grep -v "^[[:space:]]*\/\/" | head -n 1)
if [ -z "$active_proxy_string" ]; then if [ -z "$active_proxy_string" ]; then
log "Proxy string is not set. Exit" log "[critical] Proxy string is not set. Exit"
exit 1 exit 1
fi fi
if [[ "$active_proxy_string" =~ ^ss:// ]]; then if [[ "$active_proxy_string" =~ ^ss:// ]]; then
sing_box_config_shadowsocks "$section" "$active_proxy_string" config_get ss_uot $section "ss_uot"
sing_box_config_shadowsocks "$section" "$active_proxy_string" "$ss_uot"
elif [[ "$active_proxy_string" =~ ^vless:// ]]; then elif [[ "$active_proxy_string" =~ ^vless:// ]]; then
sing_box_config_vless "$section" "$active_proxy_string" sing_box_config_vless "$section" "$active_proxy_string"
else else
@@ -863,7 +981,7 @@ sing_box_rule_dns() {
sing_box_config_check() { sing_box_config_check() {
if ! sing-box -c $SING_BOX_CONFIG check >/dev/null 2>&1; then if ! sing-box -c $SING_BOX_CONFIG check >/dev/null 2>&1; then
log "Sing-box configuration is invalid" log "[critical] Sing-box configuration is invalid"
exit 1 exit 1
fi fi
} }
@@ -903,6 +1021,7 @@ sing_box_config_outbound_json() {
sing_box_config_shadowsocks() { sing_box_config_shadowsocks() {
local section="$1" local section="$1"
local STRING="$2" local STRING="$2"
ss_uot="${3:-0}"
if echo "$STRING" | cut -d'/' -f3 | cut -d'@' -f1 | base64 -d 2>/dev/null | grep -q ":"; then if echo "$STRING" | cut -d'/' -f3 | cut -d'@' -f1 | base64 -d 2>/dev/null | grep -q ":"; then
local encrypted_part=$(echo "$STRING" | cut -d'/' -f3 | cut -d'@' -f1 | base64 -d 2>/dev/null ) local encrypted_part=$(echo "$STRING" | cut -d'/' -f3 | cut -d'@' -f1 | base64 -d 2>/dev/null )
@@ -926,6 +1045,7 @@ sing_box_config_shadowsocks() {
--argjson port "$port" \ --argjson port "$port" \
--arg method "$method" \ --arg method "$method" \
--arg password "$password" \ --arg password "$password" \
--argjson ss_uot "$ss_uot" \
'. | '. |
.outbounds |= ( .outbounds |= (
map( map(
@@ -935,9 +1055,8 @@ sing_box_config_shadowsocks() {
"server": $server, "server": $server,
"server_port": ($port | tonumber), "server_port": ($port | tonumber),
"method": $method, "method": $method,
"password": $password, "password": $password
"udp_over_tcp": { "enabled": true, "version": 2 } } + (if $ss_uot == 1 then { "udp_over_tcp": { "enabled": true, "version": 2 } } else {} end)
}
else . end else . end
) + ) +
( (
@@ -948,9 +1067,8 @@ sing_box_config_shadowsocks() {
"server": $server, "server": $server,
"server_port": ($port | tonumber), "server_port": ($port | tonumber),
"method": $method, "method": $method,
"password": $password, "password": $password
"udp_over_tcp": { "enabled": true, "version": 2 } } + (if $ss_uot == 1 then { "udp_over_tcp": { "enabled": true, "version": 2 } } else {} end)]
}]
else [] end else [] end
) )
)' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG )' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
@@ -1286,7 +1404,7 @@ list_subnets_download() {
"discord") "discord")
URL=$SUBNETS_DISCORD URL=$SUBNETS_DISCORD
nft add set inet $table podkop_discord_subnets { type ipv4_addr\; flags interval\; auto-merge\; } nft add set inet $table podkop_discord_subnets { type ipv4_addr\; flags interval\; auto-merge\; }
nft add rule inet $table mangle iifname "br-lan" ip daddr @podkop_discord_subnets udp dport { 50000-65535 } meta mark set 0x105 counter nft add rule inet $table mangle iifname "$SRC_INTERFACE" ip daddr @podkop_discord_subnets udp dport { 50000-65535 } meta mark set 0x105 counter
;; ;;
*) *)
return return
@@ -1526,9 +1644,11 @@ sing_box_rules_source_ip_cidr() {
## nftables ## nftables
list_all_traffic_from_ip() { list_all_traffic_from_ip() {
local ip="$1" local ip="$1"
if ! nft list chain inet PodkopTable mangle | grep -q "ip saddr $ip"; then local table="PodkopTable"
nft add set inet PodkopTable localv4 { type ipv4_addr\; flags interval\; }
nft add element inet PodkopTable localv4 { \ if ! nft list chain inet $table mangle | grep -q "ip saddr $ip"; then
nft add set inet $table localv4 { type ipv4_addr\; flags interval\; }
nft add element inet $table localv4 { \
0.0.0.0/8, \ 0.0.0.0/8, \
10.0.0.0/8, \ 10.0.0.0/8, \
127.0.0.0/8, \ 127.0.0.0/8, \
@@ -1542,8 +1662,8 @@ list_all_traffic_from_ip() {
203.0.113.0/24, \ 203.0.113.0/24, \
224.0.0.0/4, \ 224.0.0.0/4, \
240.0.0.0-255.255.255.255 } 240.0.0.0-255.255.255.255 }
nft insert rule inet PodkopTable mangle iifname "br-lan" ip saddr $ip meta l4proto { tcp, udp } meta mark set 0x105 counter nft insert rule inet $table mangle iifname "$SRC_INTERFACE" ip saddr $ip meta l4proto { tcp, udp } meta mark set 0x105 counter
nft insert rule inet PodkopTable mangle ip saddr $ip ip daddr @localv4 return nft insert rule inet $table mangle ip saddr $ip ip daddr @localv4 return
fi fi
} }
@@ -1717,28 +1837,30 @@ check_fakeip() {
return 1 return 1
fi fi
local test_domain="fakeip.tech-domain.club" local test_domain="$TEST_DOMAIN"
# Additional DNS checks with different servers
nolog "Testing DNS resolution with default DNS server" nolog "Testing DNS resolution with default DNS server"
echo "=== Testing with default DNS server ===" echo "=== Testing with default DNS server ==="
nslookup $test_domain nslookup -timeout=2 $test_domain
echo "" echo ""
nolog "Testing DNS resolution with Google DNS (8.8.8.8)" nolog "Finding a working DNS resolver..."
echo "=== Testing with Google DNS (8.8.8.8) ===" local working_resolver=$(find_working_resolver)
nslookup $test_domain 8.8.8.8 if [ -z "$working_resolver" ]; then
echo "" nolog "No working resolver found, skipping resolver check"
else
nolog "Testing DNS resolution with Cloudflare DNS (1.1.1.1)" nolog "Using resolver: $working_resolver"
echo "=== Testing with Cloudflare DNS (1.1.1.1) ==="
nslookup $test_domain 1.1.1.1 nolog "Testing DNS resolution with working resolver ($working_resolver)"
echo "" echo "=== Testing with working resolver ($working_resolver) ==="
nslookup -timeout=2 $test_domain $working_resolver
echo ""
fi
# Main FakeIP check # Main FakeIP check
nolog "Testing DNS resolution for $test_domain using 127.0.0.42" nolog "Testing DNS resolution for $test_domain using 127.0.0.42"
echo "=== Testing with FakeIP DNS (127.0.0.42) ===" echo "=== Testing with FakeIP DNS (127.0.0.42) ==="
local result=$(nslookup $test_domain 127.0.0.42 2>&1) local result=$(nslookup -timeout=2 $test_domain 127.0.0.42 2>&1)
echo "$result" echo "$result"
if echo "$result" | grep -q "198.18"; then if echo "$result" | grep -q "198.18"; then
@@ -1775,12 +1897,24 @@ check_fakeip() {
check_logs() { check_logs() {
nolog "Showing podkop logs from system journal..." nolog "Showing podkop logs from system journal..."
if command -v logread >/dev/null 2>&1; then if ! command -v logread >/dev/null 2>&1; then
logread -e podkop | tail -n 50
else
nolog "Error: logread command not found" nolog "Error: logread command not found"
return 1 return 1
fi fi
# Get all logs first
local all_logs=$(logread)
# Find the last occurrence of "Starting podkop"
local start_line=$(echo "$all_logs" | grep -n "podkop.*Starting podkop" | tail -n 1 | cut -d: -f1)
if [ -z "$start_line" ]; then
nolog "No 'Starting podkop' message found in logs"
return 1
fi
# Output all logs from the last start
echo "$all_logs" | tail -n +"$start_line"
} }
show_sing_box_config() { show_sing_box_config() {
@@ -1836,6 +1970,7 @@ show_config() {
-e 's/\(ss:\/\/[^@]*@\)/ss:\/\/MASKED@/g' \ -e 's/\(ss:\/\/[^@]*@\)/ss:\/\/MASKED@/g' \
-e 's/\(pbk=[^&]*\)/pbk=MASKED/g' \ -e 's/\(pbk=[^&]*\)/pbk=MASKED/g' \
-e 's/\(sid=[^&]*\)/sid=MASKED/g' \ -e 's/\(sid=[^&]*\)/sid=MASKED/g' \
-e 's/\(option dns_server '\''[^'\'']*\.dns\.nextdns\.io'\''\)/option dns_server '\''MASKED.dns.nextdns.io'\''/g' \
> "$tmp_config" > "$tmp_config"
cat "$tmp_config" cat "$tmp_config"
@@ -1940,8 +2075,69 @@ get_status() {
echo "{\"running\":$running,\"enabled\":$enabled,\"status\":\"$status\"}" echo "{\"running\":$running,\"enabled\":$enabled,\"status\":\"$status\"}"
} }
check_dns_available() {
local dns_type=$(uci get podkop.main.dns_type 2>/dev/null)
local dns_server=$(uci get podkop.main.dns_server 2>/dev/null)
local is_available=0
local status="unavailable"
local local_dns_working=0
local local_dns_status="unavailable"
# Mask NextDNS ID if present
local display_dns_server="$dns_server"
if echo "$dns_server" | grep -q "\.dns\.nextdns\.io$"; then
local nextdns_id=$(echo "$dns_server" | cut -d'.' -f1)
display_dns_server="$(echo "$nextdns_id" | sed 's/./*/g').dns.nextdns.io"
fi
if [ "$dns_type" = "doh" ]; then
local result=""
if echo "$dns_server" | grep -q "quad9.net" || \
echo "$dns_server" | grep -qE "^9\.9\.9\.(9|10|11)$|^149\.112\.112\.(112|10|11)$|^2620:fe::(fe|9|10|11)$|^2620:fe::fe:(10|11)$"; then
result=$(curl --connect-timeout 5 -s -H "accept: application/dns-json" "https://$dns_server:5053/dns-query?name=itdog.info&type=A")
else
result=$(curl --connect-timeout 5 -s -H "accept: application/dns-json" "https://$dns_server/dns-query?name=itdog.info&type=A")
if [ $? -eq 0 ] && echo "$result" | grep -q "data"; then
is_available=1
status="available"
else
result=$(curl --connect-timeout 5 -s -H "accept: application/dns-json" "https://$dns_server/resolve?name=itdog.info&type=A")
fi
fi
if [ $? -eq 0 ] && echo "$result" | grep -q "data"; then
is_available=1
status="available"
fi
elif [ "$dns_type" = "dot" ]; then
(nc "$dns_server" 853 </dev/null >/dev/null 2>&1) & pid=$!
sleep 2
if kill -0 $pid 2>/dev/null; then
kill $pid 2>/dev/null
wait $pid 2>/dev/null
else
is_available=1
status="available"
fi
elif [ "$dns_type" = "udp" ]; then
if nslookup -timeout=2 itdog.info $dns_server >/dev/null 2>&1; then
is_available=1
status="available"
fi
fi
# Check if local DNS resolver is working
if nslookup -timeout=2 $TEST_DOMAIN 127.0.0.1 >/dev/null 2>&1; then
local_dns_working=1
local_dns_status="available"
fi
echo "{\"dns_type\":\"$dns_type\",\"dns_server\":\"$display_dns_server\",\"is_available\":$is_available,\"status\":\"$status\",\"local_dns_working\":$local_dns_working,\"local_dns_status\":\"$local_dns_status\"}"
}
sing_box_add_secure_dns_probe_domain() { sing_box_add_secure_dns_probe_domain() {
local domain="fakeip.tech-domain.club" local domain="$TEST_DOMAIN"
local override_port=8443 local override_port=8443
log "Adding DNS probe domain ${domain} to fakeip-server configuration" log "Adding DNS probe domain ${domain} to fakeip-server configuration"
@@ -1976,9 +2172,8 @@ case "$1" in
stop) stop)
stop stop
;; ;;
restart) reload)
stop reload
start
;; ;;
main) main)
main main
@@ -2034,8 +2229,11 @@ case "$1" in
get_sing_box_status) get_sing_box_status)
get_sing_box_status get_sing_box_status
;; ;;
check_dns_available)
check_dns_available
;;
*) *)
echo "Usage: $0 {start|stop|restart|reload|enable|disable|main|list_update|check_proxy|check_nft|check_github|check_logs|check_sing_box_connections|check_sing_box_logs|check_fakeip|check_dnsmasq|show_config|show_version|show_sing_box_config|show_luci_version|show_sing_box_version|show_system_info|get_status|get_sing_box_status}" echo "Usage: $0 {start|stop|reload|enable|disable|main|list_update|check_proxy|check_nft|check_github|check_logs|check_sing_box_connections|check_sing_box_logs|check_fakeip|check_dnsmasq|show_config|show_version|show_sing_box_config|show_luci_version|show_sing_box_version|show_system_info|get_status|get_sing_box_status|check_dns_available}"
exit 1 exit 1
;; ;;
esac esac