Compare commits

...

38 Commits

Author SHA1 Message Date
itdoginfo
f2268fd494 v0.3.41. Improved Diagnotics: WAN, WARP, versions, etc 2025-04-29 12:53:29 +03:00
itdoginfo
19897afcdd v0.3.40. Improved Diagnotics 2025-04-28 00:33:07 +03:00
itdoginfo
0e2ea60f01 v0.3.39. Added global check button 2025-04-27 19:29:34 +03:00
itdoginfo
2dc5944961 Fix https-dns-proxy --force-depends 2025-04-27 18:07:58 +03:00
itdoginfo
f65de36804 Detect https-dns-proxy 2025-04-27 15:50:37 +03:00
itdoginfo
19541f8bb3 v0.3.38. fix reload config luci 2025-04-26 22:35:11 +03:00
itdoginfo
aa42c707fe v0.3.37 2025-04-26 17:49:28 +03:00
itdoginfo
bf96f93987 Fix kill stderr. Return if 127.0.0.42 exists 2025-04-26 17:49:04 +03:00
itdoginfo
ff9aad8947 Option enable iface mon 2025-04-26 17:47:52 +03:00
itdoginfo
d9718617bd Option enable iface mon 2025-04-26 17:47:42 +03:00
itdoginfo
e865c9f324 Validate raw network. Path for DoH. Bool for iface monitoring 2025-04-26 17:47:08 +03:00
itdoginfo
7df8bb5826 rmempty proxy url string 2025-04-25 19:29:31 +03:00
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
9 changed files with 486 additions and 117 deletions

View File

@@ -73,16 +73,14 @@ Luci: Services/podkop
**Custom subnets enable** - Добавить подсети или IP-адреса. Для подсетей задать маску.
# Известные баги
- [x] Не отрабатывает service podkop stop, если podkop запущен и не может, к пример, зарезолвить домен с сломанным DNS
- [x] Update list из remote url domain не удаляет старые домены. А добавляет новые. Для подсетей тоже самое скорее всего. Пересоздавать ruleset?
# ToDo
Этот раздел не означает задачи, которые нужно брать и делать. Это общий список хотелок. Если вы хотите помочь, пожалуйста, спросите сначала в телеграмме.
- [ ] Сделать галку запрещающую подкопу редачить dhcp. Допилить в исключение вместе с пустыми полями proxy и vpn (нужно wiki)
- [ ] Рестарт сервиса без рестарта dnsmasq
- [ ] `ash: can't kill pid 9848: No such process` при обновлении
- [ ] Не грузится диагностика полностью при одной нерабочей комманде. Подумать как это можно дебажить легко. https://t.me/itdogchat/142500/378956
- [ ] При добавлении github ломается скачивание скрипта установки и любые другие скрипты с github соотвественно. Скорее всего нужно делать опцией добавление в nft самого роутера как src.
Диагностика
- [x] Используется ли warp. Сравнивать endpoint с префиксами CF
Низкий приоритет
- [ ] Галочка, которая режет доступ к doh серверам
@@ -94,6 +92,23 @@ Luci: Services/podkop
- [ ] Unit тесты (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 работать не будет.
# Bad WAN
При использовании опции **Interface monitoring** необходимо рестартовать podkop, чтоб init.d подхватил это
```
service podkop restart
```
# Разработка
Есть два варианта:
- Просто поставить пакет на роутер или виртуалку и прям редактировать через SFTP (opkg install openssh-sftp-server)

View File

@@ -42,12 +42,14 @@ main() {
echo "Installed podkop..."
add_tunnel
fi
if command -v curl &> /dev/null; then
check_response=$(curl -s "https://api.github.com/repos/itdoginfo/podkop/releases/latest")
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
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
@@ -158,13 +160,13 @@ add_tunnel() {
;;
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"
break
;;
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"
break
;;
@@ -246,8 +248,8 @@ install_awg_packages() {
fi
fi
if opkg list-installed | grep -q luci-app-amneziawg; then
echo "luci-app-amneziawg already installed"
if opkg list-installed | grep -qE 'luci-app-amneziawg|luci-proto-amneziawg'; then
echo "luci-app-amneziawg or luci-proto-amneziawg already installed"
else
LUCI_APP_AMNEZIAWG_FILENAME="luci-app-amneziawg${PKGPOSTFIX}"
DOWNLOAD_URL="${BASE_URL}v${VERSION}/${LUCI_APP_AMNEZIAWG_FILENAME}"
@@ -423,6 +425,25 @@ check_system() {
exit 1
fi
if opkg list-installed | grep -q https-dns-proxy; then
printf "\033[31;1mСonflicting package detected: https-dns-proxy. Remove? yes/no\033[0m\n"
while true; do
read -r -p '' DNSPROXY
case $DNSPROXY in
yes|y|Y|yes)
opkg remove --force-depends luci-app-https-dns-proxy https-dns-proxy
break
;;
*)
echo "Exit"
exit 1
;;
esac
done
fi
if opkg list-installed | grep -qE "iptables|kmod-iptab"; then
printf "\033[31;1mFound incompatible iptables packages. If you're using FriendlyWrt: https://t.me/itdogchat/44512/181082\033[0m\n"
fi

View File

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

View File

@@ -12,6 +12,8 @@ const STATUS_COLORS = {
WARNING: '#ff9800'
};
const ERROR_POLL_INTERVAL = 5000; // 5 seconds
async function safeExec(command, args = [], timeout = 7000) {
try {
const controller = new AbortController();
@@ -60,6 +62,23 @@ function getNetworkInterfaces(o, section_id, excludeInterfaces = []) {
});
}
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) {
const s = section;
@@ -80,6 +99,7 @@ function createConfigSection(section, map, network) {
o = s.taboption('basic', form.TextValue, 'proxy_string', _('Proxy Configuration URL'), _(''));
o.depends('proxy_config_type', 'url');
o.rows = 5;
o.rmempty = false;
o.ucisection = s.section;
o.sectionDescriptions = new Map();
o.placeholder = 'vless://uuid@server:port?type=tcp&security=tls#main\n// backup ss://method:pass@server:port\n// backup2 vless://uuid@server:port?type=grpc&security=reality#alt';
@@ -204,9 +224,9 @@ function createConfigSection(section, map, network) {
let params = new URLSearchParams(queryString.split('#')[0]);
let type = params.get('type');
const validTypes = ['tcp', 'udp', 'grpc', 'http'];
const validTypes = ['tcp', 'raw', 'udp', 'grpc', 'http'];
if (!type || !validTypes.includes(type)) {
return _('Invalid VLESS URL: type must be one of tcp, udp, grpc, http');
return _('Invalid VLESS URL: type must be one of tcp, raw, udp, grpc, http');
}
let security = params.get('security');
@@ -259,7 +279,7 @@ function createConfigSection(section, map, network) {
o.depends('mode', 'vpn');
o.ucisection = s.section;
o.load = function (section_id) {
return getNetworkInterfaces(this, section_id, ['br-lan', 'eth0', 'eth1', 'wan', 'phy0-ap0', 'phy1-ap0', 'pppoe-wan']).then(() => {
return getNetworkInterfaces(this, section_id, ['br-lan', 'eth0', 'eth1', 'wan', 'phy0-ap0', 'phy1-ap0', 'pppoe-wan', 'lan']).then(() => {
return this.super('load', section_id);
});
};
@@ -786,7 +806,12 @@ let createStatusSection = function (podkopStatus, singboxStatus, podkop, luci, s
bypassStatus.message
])
])
])
]),
ButtonFactory.createModalButton({
label: _('Global check'),
command: 'global_check',
title: _('Click here for all the info')
})
]),
// Version Information Panel
@@ -813,20 +838,34 @@ function checkDNSAvailability() {
return new Promise(async (resolve) => {
try {
const dnsStatusResult = await safeExec('/usr/bin/podkop', ['check_dns_available']);
const dnsStatus = JSON.parse(dnsStatusResult.stdout || '{"dns_type":"unknown","dns_server":"unknown","is_available":0,"status":"unknown","local_dns_working":0,"local_dns_status":"unknown"}');
if (!dnsStatusResult || !dnsStatusResult.stdout) {
return resolve({
remote: createStatus('error', 'DNS check timeout', 'WARNING'),
local: createStatus('error', 'DNS check timeout', 'WARNING')
});
}
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');
try {
const dnsStatus = JSON.parse(dnsStatusResult.stdout);
const localStatus = dnsStatus.local_dns_working ?
createStatus('available', 'Router DNS working', 'SUCCESS') :
createStatus('unavailable', 'Router DNS not working', 'ERROR');
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');
return resolve({
remote: remoteStatus,
local: localStatus
});
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'),
@@ -836,6 +875,84 @@ function checkDNSAvailability() {
});
}
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({
async render() {
document.head.insertAdjacentHTML('beforeend', `
@@ -932,9 +1049,9 @@ return view.extend({
return true;
}
const domainRegex = /^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.[a-zA-Z]{2,}$/;
const domainRegex = /^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.[a-zA-Z]{2,}(\/[^\s]*)?$/;
if (!domainRegex.test(value)) {
return _('Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com');
return _('Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH');
}
return true;
@@ -993,6 +1110,25 @@ return view.extend({
});
};
o = mainSection.taboption('additional', form.Flag, 'mon_restart_ifaces', _('Interface monitoring'), _('Interface monitoring for bad WAN'));
o.default = '0';
o.rmempty = false;
o.ucisection = 'main';
o = mainSection.taboption('additional', form.MultiValue, 'restart_ifaces', _('Interface for monitoring'), _('Select the WAN interfaces to be monitored'));
o.ucisection = 'main';
o.depends('mon_restart_ifaces', '1');
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)
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';
@@ -1334,8 +1470,10 @@ return view.extend({
const diagnosticsContainer = document.getElementById('diagnostics-status');
if (document.hidden) {
stopDiagnosticsUpdates();
stopErrorPolling();
} else if (diagnosticsContainer && diagnosticsContainer.hasAttribute('data-loading')) {
startDiagnosticsUpdates();
startErrorPolling();
}
});
@@ -1346,6 +1484,7 @@ return view.extend({
if (!this.hasAttribute('data-loading')) {
this.setAttribute('data-loading', 'true');
startDiagnosticsUpdates();
startErrorPolling();
}
});
}
@@ -1361,9 +1500,11 @@ return view.extend({
if (container && !container.hasAttribute('data-loading')) {
container.setAttribute('data-loading', 'true');
startDiagnosticsUpdates();
startErrorPolling();
}
} else {
stopDiagnosticsUpdates();
stopErrorPolling();
}
}
});
@@ -1374,6 +1515,7 @@ return view.extend({
if (container && !container.hasAttribute('data-loading')) {
container.setAttribute('data-loading', 'true');
startDiagnosticsUpdates();
startErrorPolling();
}
}
}

View File

@@ -88,8 +88,8 @@ msgstr "Введите имена доменов без протоколов (п
msgid "User Domains List"
msgstr "Список пользовательских доменов"
msgid "Enter domain names separated by comma, space or newline (example: sub.example.com, example.com or one domain per line)"
msgstr "Введите имена доменов через запятую, пробел или новую строку (пример: sub.example.com, example.com или один домен на строку)"
msgid "Enter domain names separated by comma, space or newline. You can add comments after //"
msgstr "Введите имена доменов, разделяя их запятой, пробелом или с новой строки. Вы можете добавлять комментарии после //"
msgid "Local Domain Lists"
msgstr "Локальные списки доменов"
@@ -556,6 +556,9 @@ msgstr "Путь должен содержать хотя бы одну дире
msgid "Invalid path format. Must be like /tmp/cache.db"
msgstr "Неверный формат пути. Пример: /tmp/cache.db"
msgid "Select the network interface from which the traffic will originate"
msgstr "Выберите сетевой интерфейс, с которого будет исходить трафик"
msgid "Copy to Clipboard"
msgstr "Копировать в буфер обмена"
@@ -812,4 +815,7 @@ msgid "available"
msgstr "доступен"
msgid "unavailable"
msgstr "недоступен"
msgstr "недоступен"
msgid "Apply for SS2022"
msgstr "Применить для SS2022"

View File

@@ -1,7 +1,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=podkop
PKG_VERSION:=0.3.31
PKG_VERSION:=0.3.41
PKG_RELEASE:=1
PKG_MAINTAINER:=ITDog <podkop@itdog.info>
@@ -13,6 +13,7 @@ define Package/podkop
SECTION:=net
CATEGORY:=Network
DEPENDS:=+sing-box +curl +jq +kmod-nft-tproxy +coreutils-base64
CONFLICTS:=https-dns-proxy
TITLE:=Domain routing app
URL:=https://itdog.info
PKGARCH:=all

View File

@@ -36,4 +36,6 @@ config main 'main'
option dns_rewrite_ttl '60'
option cache_file '/tmp/cache.db'
list iface 'br-lan'
option mon_restart_ifaces '0'
#list restart_ifaces 'wan'
option ss_uot '0'

View File

@@ -6,38 +6,16 @@ USE_PROCD=1
script=$(readlink "$initscript")
NAME="$(basename ${script:-$initscript})"
config_load "$NAME"
resolv_conf="/etc/resolv.conf"
start_service() {
echo "Start podkop"
sing_box_version=$(sing-box version | head -n 1 | awk '{print $3}')
required_version="1.11.1"
config_get mon_restart_ifaces "main" "mon_restart_ifaces"
config_get restart_ifaces "main" "restart_ifaces"
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_set_param command /bin/sh -c "/usr/bin/podkop start"
procd_set_param command /usr/bin/podkop start
[ "$mon_restart_ifaces" = "1" ] && [ -n "$restart_ifaces" ] && procd_set_param netdev $restart_ifaces
procd_set_param stdout 1
procd_set_param stderr 1
procd_close_instance
@@ -47,17 +25,23 @@ stop_service() {
/usr/bin/podkop stop
}
restart_service() {
stop
start
}
reload_service() {
stop
start
/usr/bin/podkop reload > /dev/null 2>&1
}
service_triggers() {
echo "service_triggers start"
procd_add_config_trigger "config.change" "$NAME" "$initscript" reload 'on_config_change'
config_get mon_restart_ifaces "main" "mon_restart_ifaces"
config_get restart_ifaces "main" "restart_ifaces"
procd_open_trigger
procd_add_config_trigger "config.change" "$NAME" "$initscript" restart 'on_config_change'
if [ "$mon_restart_ifaces" = "1" ]; then
for iface in $restart_ifaces; do
procd_add_reload_interface_trigger $iface
done
fi
procd_close_trigger
}

View File

@@ -22,6 +22,7 @@ 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.
TEST_DOMAIN="fakeip.tech-domain.club"
INTERFACES_LIST=""
SRC_INTERFACE=""
RESOLV_CONF="/etc/resolv.conf"
log() {
local message="$1"
@@ -44,7 +45,26 @@ nolog() {
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
migration
config_foreach process_validate_service
@@ -112,7 +132,12 @@ start() {
sing_box_config_check
/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 interface "main" "interface"
@@ -126,13 +151,13 @@ start() {
fi
}
stop() {
stop_main() {
log "Stopping the podkop"
if [ -f /var/run/podkop_list_update.pid ]; then
pid=$(cat /var/run/podkop_list_update.pid)
if kill -0 "$pid"; then
kill "$pid"
kill "$pid" 2>/dev/null
log "Stopped list_update"
fi
rm -f /var/run/podkop_list_update.pid
@@ -140,11 +165,6 @@ stop() {
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
log "Flush nft"
@@ -164,8 +184,22 @@ stop() {
log "Stop sing-box"
/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
@@ -337,7 +371,8 @@ dnsmasq_add_resolver() {
uci -q delete dhcp.@dnsmasq[0].podkop_server
for server in $(uci get dhcp.@dnsmasq[0].server 2>/dev/null); do
if [[ "$server" == "127.0.0.42" ]]; then
log "Dnsmasq save config error: server=127.0.0.42"
log "Dnsmasq save config error: server=127.0.0.42 is already configured. Skip editing DHCP"
return
else
uci add_list dhcp.@dnsmasq[0].podkop_server="$server"
fi
@@ -568,10 +603,12 @@ sing_box_uci() {
log "Change sing-box UCI config"
fi
if grep -q '#\s*list ifaces' "$config"; then
sed -i '/ifaces/s/#//g' $config
log "Uncommented list ifaces"
fi
[ -f /etc/rc.d/S99sing-box ] && log "Disable sing-box" && /etc/init.d/sing-box disable
# if grep -q '#\s*list ifaces' "$config"; then
# sed -i '/ifaces/s/#//g' $config
# log "Uncommented list ifaces"
# fi
}
add_socks5_for_section() {
@@ -842,7 +879,7 @@ sing_box_outdound() {
config_get interface "$section" "interface"
if [ -z "$interface" ]; then
log "VPN interface is not set. Exit"
log "[critical] VPN interface is not set. Exit"
exit 1
fi
@@ -868,7 +905,7 @@ sing_box_outdound() {
active_proxy_string=$(echo "$proxy_string" | grep -v "^[[:space:]]*\/\/" | head -n 1)
if [ -z "$active_proxy_string" ]; then
log "Proxy string is not set. Exit"
log "[critical] Proxy string is not set. Exit"
exit 1
fi
@@ -941,7 +978,7 @@ sing_box_rule_dns() {
sing_box_config_check() {
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
fi
}
@@ -1790,6 +1827,7 @@ check_sing_box_logs() {
}
check_fakeip() {
# Not used
nolog "Checking fakeip functionality..."
if ! command -v nslookup >/dev/null 2>&1; then
@@ -1857,12 +1895,24 @@ check_fakeip() {
check_logs() {
nolog "Showing podkop logs from system journal..."
if command -v logread >/dev/null 2>&1; then
logread -e podkop | tail -n 50
else
if ! command -v logread >/dev/null 2>&1; then
nolog "Error: logread command not found"
return 1
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() {
@@ -1900,7 +1950,7 @@ show_sing_box_config() {
}
show_config() {
nolog "Current podkop configuration:"
nolog "📄 Current podkop configuration:"
if [ ! -f /etc/config/podkop ]; then
nolog "Configuration file not found"
@@ -1918,6 +1968,8 @@ show_config() {
-e 's/\(ss:\/\/[^@]*@\)/ss:\/\/MASKED@/g' \
-e 's/\(pbk=[^&]*\)/pbk=MASKED/g' \
-e 's/\(sid=[^&]*\)/sid=MASKED/g' \
-e 's/\(option dns_server '\''[^'\'']*\.dns\.nextdns\.io'\''\)/option dns_server '\''MASKED.dns.nextdns.io'\''/g' \
-e "s|\(option dns_server 'dns\.nextdns\.io\)/[^']*|\1/MASKED|"
> "$tmp_config"
cat "$tmp_config"
@@ -1925,17 +1977,17 @@ show_config() {
}
show_version() {
local version=$(opkg info podkop | grep -m 1 "Version:" | cut -d' ' -f2)
local version=$(opkg list-installed podkop | awk '{print $3}')
echo "$version"
}
show_luci_version() {
local version=$(opkg info luci-app-podkop | grep -m 1 "Version:" | cut -d' ' -f2)
local version=$(opkg list-installed luci-app-podkop | awk '{print $3}')
echo "$version"
}
show_sing_box_version() {
local version=$(opkg info sing-box | grep -m 1 "Version:" | cut -d' ' -f2)
local version=$(sing-box version | head -n 1 | awk '{print $3}')
echo "$version"
}
@@ -2029,34 +2081,47 @@ check_dns_available() {
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"
elif echo "$dns_server" | grep -q "^dns\.nextdns\.io/"; then
local masked_path=$(echo "$dns_server" | cut -d'/' -f2- | sed 's/./*/g')
display_dns_server="dns.nextdns.io/$masked_path"
fi
if [ "$dns_type" = "doh" ]; then
# Different DoH providers use different endpoints and formats
local result=""
# Try common DoH endpoints and check for valid responses
# First try /dns-query endpoint (Cloudflare, AdGuard DNS, etc.)
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"
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
# If that fails, try /resolve endpoint (Google DNS)
result=$(curl --connect-timeout 5 -s -H "accept: application/dns-json" "https://$dns_server/resolve?name=itdog.info&type=A")
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
elif [ "$dns_type" = "dot" ]; then
nc $dns_server 853 </dev/null >/dev/null 2>&1 & pid=$!
(sleep 3; kill $pid 2>/dev/null) & killpid=$!
wait $pid >/dev/null 2>&1
if [ $? -eq 0 ]; then
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
kill $killpid 2>/dev/null
elif [ "$dns_type" = "udp" ]; then
if nslookup -timeout=2 itdog.info $dns_server >/dev/null 2>&1; then
is_available=1
@@ -2070,7 +2135,7 @@ check_dns_available() {
local_dns_status="available"
fi
echo "{\"dns_type\":\"$dns_type\",\"dns_server\":\"$dns_server\",\"is_available\":$is_available,\"status\":\"$status\",\"local_dns_working\":$local_dns_working,\"local_dns_status\":\"$local_dns_status\"}"
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() {
@@ -2102,6 +2167,137 @@ sing_box_add_secure_dns_probe_domain() {
log "DNS probe domain ${domain} configured with override to port ${override_port}"
}
global_check() {
nolog "📡 Global check run!"
nolog "Podkop $(opkg list-installed podkop | awk '{print $3}')"
nolog "LuCi App $(opkg list-installed luci-app-podkop | awk '{print $3}')"
nolog "Sing-box $(sing-box version | head -n 1 | awk '{print $3}')"
nolog "$(grep OPENWRT_RELEASE /etc/os-release | cut -d'"' -f2)"
nolog "Device: $(cat /tmp/sysinfo/model)"
printf "\n"
show_config
printf "\n"
nolog "Checking fakeip functionality..."
nolog "➡️ DNS resolution: system DNS server"
nslookup -timeout=2 $TEST_DOMAIN
local working_resolver=$(find_working_resolver)
if [ -z "$working_resolver" ]; then
nolog "❌ No working resolver found, skipping resolver check"
else
nolog "➡️ DNS resolution: external resolver ($working_resolver)"
nslookup -timeout=2 $TEST_DOMAIN $working_resolver
fi
# Main FakeIP check
nolog "➡️ DNS resolution: sing-box DNS server (127.0.0.42)"
local result=$(nslookup -timeout=2 $TEST_DOMAIN 127.0.0.42 2>&1)
echo "$result"
if echo "$result" | grep -q "198.18"; then
nolog "✅ FakeIP is working correctly! Domain resolved to FakeIP range (198.18.x.x)"
else
nolog "❌ FakeIP test failed. Domain did not resolve to FakeIP range"
nolog "Checking if sing-box is running..."
if ! pgrep -f "sing-box" >/dev/null; then
nolog "sing-box is not running"
else
nolog "sing-box is running, but FakeIP might not be configured correctly"
nolog "Checking DNS configuration in sing-box..."
if [ -f "$SING_BOX_CONFIG" ]; then
local fakeip_enabled=$(jq -r '.dns.fakeip.enabled' "$SING_BOX_CONFIG")
local fakeip_range=$(jq -r '.dns.fakeip.inet4_range' "$SING_BOX_CONFIG")
nolog "FakeIP enabled: $fakeip_enabled"
nolog "FakeIP range: $fakeip_range"
local dns_rules=$(jq -r '.dns.rules[] | select(.server == "fakeip-server") | .domain' "$SING_BOX_CONFIG")
nolog "FakeIP domain: $dns_rules"
else
nolog "sing-box config file not found"
fi
fi
fi
printf "\n"
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
nolog "❌ /etc/resolv.conf contains an external nameserver:"
cat /etc/resolv.conf
echo ""
else
nolog "✅ /etc/resolv.conf OK"
fi
cachesize="$(uci get dhcp.@dnsmasq[0].cachesize 2>/dev/null)"
noresolv="$(uci get dhcp.@dnsmasq[0].noresolv 2>/dev/null)"
server="$(uci get dhcp.@dnsmasq[0].server 2>/dev/null)"
if [ "$cachesize" != "0" ] || [ "$noresolv" != "1" ] || [ "$server" != "127.0.0.42" ]; then
nolog "❌ The configuration differs from the template. 📄 DHCP config:"
awk '/^config /{p=($2=="dnsmasq")} p' /etc/config/dhcp
elif [ "$(uci get podkop.main.dont_touch_dhcp 2>/dev/null)" = "1" ]; then
nolog "⚠️ Enable dont_touch_dhcp. 📄 DHCP config:"
awk '/^config /{p=($2=="dnsmasq")} p' /etc/config/dhcp
else
nolog "✅ /etc/config/dhcp"
fi
if ! pgrep -f "sing-box" >/dev/null; then
nolog "❌ sing-box is not running"
else
nolog "✅ sing-box is running"
fi
nolog "📄 NFT Table Podkop"
if ! nft list table inet PodkopTable >/dev/null 2>&1; then
nolog "PodkopTable not found"
else
nft list table inet PodkopTable
fi
nolog "📄 WAN config"
if uci show network.wan >/dev/null 2>&1; then
awk '
/^config / {
p = ($2 == "interface" && $3 == "'\''wan'\''")
}
p {
if ($1 == "option" && ($2 == "username" || $2 == "password")) {
print " option", $2, "'\''******'\''"
} else {
print
}
}
' /etc/config/network
else
nolog "WAN not exists"
fi
CLOUDFLARE_OCTETS="103.21 103.22 103.31 104.16 104.17 104.18 104.19 104.20 104.21 104.22 104.23 \
104.24 104.25 104.26 104.27 104.28 108.162 131.0 141.101 162.158 162.159 172.64 172.65 172.66 \
172.67 172.68 172.69 172.70 172.71 173.245 188.114 190.93 197.234 198.41"
if uci show network | grep -q endpoint_host; then
uci show network | grep endpoint_host | cut -d'=' -f2 | tr -d "'\" " | while read -r host; do
if [ "$host" = "engage.cloudflareclient.com" ]; then
nolog "⚠️ WARP detected ($host)"
continue
fi
ip_prefix=$(echo "$host" | cut -d'.' -f1,2)
if echo "$CLOUDFLARE_OCTETS" | grep -wq "$ip_prefix"; then
nolog "⚠️ WARP detected ($host)"
fi
done
fi
}
case "$1" in
start)
start
@@ -2109,9 +2305,8 @@ case "$1" in
stop)
stop
;;
restart)
stop
start
reload)
reload
;;
main)
main
@@ -2170,8 +2365,11 @@ case "$1" in
check_dns_available)
check_dns_available
;;
global_check)
global_check
;;
*)
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|check_dns_available}"
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|global_check}"
exit 1
;;
esac