diff --git a/README.md b/README.md
index e85655d..a09e793 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,13 @@
-Это альфа версия, может не работать. Обсуждение https://t.me/itdogchat - топик **Podkop**
+# Вещи, которые вам нужно знать перед установкой
-Если у вас установлен Getdomains, то его следует удалить.
+- Это альфа версия, которая находится в активной разработке. Из версии в версию что-то может меняться.
+- Основной функционал работает, но побочные штуки сейчас могут сбоить.
+- При обновлении всегда заходите в конфигурацию и проверяйте свои настройки. Конфигурация может измениться.
+- Необходимо минимум 15МБ свободного места на роутере. Роутерами с флешками на 16МБ сразу мимо.
+- При старте программы редактируется конфиг Dnsmasq.
+- Podkop редактирует конфиг sing-box. Обязательно сохраните ваш конфиг sing-box перед установкой, если он вам нужен.
+- Информация здесь может быть устаревшей. Все изменения фиксируются в телеграм-чате https://t.me/itdogchat - топик **Podkop**.
+- Если у вас установлен Getdomains, его следует удалить.
# Удаление GetDomains скриптом
```
@@ -11,9 +18,9 @@ sh <(wget -O - https://raw.githubusercontent.com/itdoginfo/domain-routing-openwr
# Установка Podkop
Пакет работает на всех архитектурах.
-Будет точно работать только на OpenWrt 23.05.
+Тестировался на OpenWrt 23.05 и OpenWrt 24.10.
-Нужен dnsmasq-full. В автоматическом режиме ставится сам. Вручную надо поставить [самостоятельно](https://github.com/itdoginfo/podkop/blob/952dd6215a2a83d65937cf9e33534c42809091ed/install.sh#L20).
+Поддержки APK на данный момент нет. APK будет сделан после того как разгребу основное.
## Автоматическая
```
@@ -53,26 +60,20 @@ Luci: Services/podkop
### Proxy
Для VLESS и Shadowsocks. Другие протоколы тоже будут, кидайте в чат примеры строк без чувствительных данных.
-Для использования этого режима нужен sing-box:
-```
-opkg update && opkg install sing-box
-```
В этом режиме просто копируйте строку в **Proxy String** и из неё автоматически настроится sing-box.
### VPN
-Здесь у вас должен быть уже настроен WG/OpenVPN/OpenConnect etc, создана Zone и Forwarding.
+Здесь у вас должен быть уже настроен WG/OpenVPN/OpenConnect etc, зона Zone и Forwarding не обязательны.
Просто выбрать интерфейс из списка.
## Настройка доменов и подсетей
-**Domain list enable** - Включить общий список.
-
-**Delist domains from main list enable** - Выключение заданных доменов из общего списка. Задавать списком.
+**Community Lists** - Включить списки комьюнити
**Subnets list enable** - Включить подсети из общего списка, выбрать из предложенных.
-**Custom domains enable** - Добавить свои домены. Задавать списком.
+**Custom domains enable** - Добавить свои домены
**Custom subnets enable** - Добавить подсети или IP-адреса. Для подсетей задать маску.
@@ -84,10 +85,15 @@ opkg update && opkg install sing-box
- [x] awg работает не стабильно
- [x] Сеть рестартится при любом раскладе
- [x] Выкл-вкл wg через luci не отрабатывает поднятие маршрута
-- [ ] Проблема скачивания списка из github release. Нужен curl -L
- [ ] Если eof после последней строки в rt_tables, то скрипт не добавляет перенос строки
+- [ ] Парсинг VLESS не отрабатывает, если в SNI два домена. Пример `sni=telegram.org%3Bwww.telegram.org`
+- [ ] `service network restart` ломает маршруты при sing-box
+- [ ] Совпадение секции с ruleset ломает конфиг sing-box
+- [ ] В каких-то случаях плохо отрабатывает localfile
# ToDo
+Этот раздел не означает задачи, которые нужно брать и делать. Это общий список хотелок. Если вы хотите помочь, пожалуйста, спросите сначала в телеграмме.
+
Сделано
- [x] Скрипт для автоматической установки.
- [x] Подсети дискорда.
@@ -127,13 +133,18 @@ opkg update && opkg install sing-box
- [x] Проверка места в скрипте install. Если доступно меньше 20MB - exit 1 c выводом колько надо и сколько доступно. + показ модели роутера
- [ ] Правило запрещающее QUIC
- [ ] Проверить обновление списков, отрабатывает ли
+- [ ] Проверка на ванильную openwrt
+- [ ] Проверка откуда установлен sing-box. Например, проверять установлен ли он из официального репозитория
+- [x] TG в сервисы
+- [ ] Выбор ткуда направлять трафик в туннель. В том числе чтоб откуда угодно, а не только br-lan
+- [ ] Диагностика: Proxy check completed successfully предположительно не показывает IP, если вернулся это IPv6.
+- [ ] Диагностика: podkop_domains: 0 elements как проверять что доходят запросы при fakeip? Мб врубать логи dnsmasq и их чекать.
+- [ ] Сделать галку запрещающую подкопу редачить dhcp. Допилить в исключение вместе с пустыми полями proxy и vpn
Приоритет 2
- [x] Списки доменов и подсетей с роутера
- [ ] Кнопка обновления списка доменов и подсетей. Запихнуть в главное меню
- [ ] IPv6
-- [ ] Придумать автонастройку DNS через stubby итд. Как лучше это реализовать. Для sing-box не нужно
-- [ ] Удаление подсетей CF из domain sets раз в N часов
Wiki
- [x] Тема
@@ -143,7 +154,7 @@ Wiki
- [x] Переменная, раз во сколько часов обновлять списки
- [ ] Галочка, которая режет доступ к doh серверам
- [ ] Свой конфиг sing-box
-- [ ] Поменять curl на wget, убрать зависимость. Проверять доступность списков лучше всего curl`ом
+- [x] Поменять curl на wget, убрать зависимость. Проверять доступность списков лучше всего curl`ом
Рефактор
- [ ] Handle для sing-box
diff --git a/install.sh b/install.sh
index f91e6a9..3592e70 100755
--- a/install.sh
+++ b/install.sh
@@ -17,28 +17,7 @@ main() {
echo "opkg update"
opkg update
-
- if opkg list-installed | grep -q dnsmasq-full; then
- echo "dnsmasq-full already installed"
- else
- echo "Installed dnsmasq-full"
- cd /tmp/ && opkg download dnsmasq-full
- opkg remove dnsmasq && opkg install dnsmasq-full --cache /tmp/
-
- [ -f /etc/config/dhcp-opkg ] && cp /etc/config/dhcp /etc/config/dhcp-old && mv /etc/config/dhcp-opkg /etc/config/dhcp
- fi
-
- openwrt_release=$(cat /etc/openwrt_release | grep -Eo [0-9]{2}[.][0-9]{2}[.][0-9]* | cut -d '.' -f 1 | tail -n 1)
- if [ $openwrt_release -ge 24 ]; then
- if uci get dhcp.@dnsmasq[0].confdir | grep -q /tmp/dnsmasq.d; then
- echo "confdir alreadt set"
- else
- printf "Setting confdir"
- uci set dhcp.@dnsmasq[0].confdir='/tmp/dnsmasq.d'
- uci commit dhcp
- fi
- fi
-
+
if [ -f "/etc/init.d/podkop" ]; then
printf "\033[32;1mPodkop is already installed. Just upgrade it? (y/n)\033[0m\n"
printf "\033[32;1my - Only upgrade podkop\033[0m\n"
@@ -49,6 +28,7 @@ main() {
case $UPDATE in
y)
echo "Upgraded podkop..."
+ sed -i '/second/d' /etc/config/podkop
break
;;
@@ -99,23 +79,17 @@ main() {
add_tunnel() {
echo "What type of VPN or proxy will be used? We also can automatically configure Wireguard and Amnezia WireGuard."
- echo "1) VLESS, Shadowsocks (A sing-box will be installed)"
- echo "2) Wireguard"
- echo "3) AmneziaWG"
- echo "4) OpenVPN"
- echo "5) OpenConnect"
- echo "6) Skip this step"
+ echo "1) Wireguard"
+ echo "2) AmneziaWG"
+ echo "3) OpenVPN"
+ echo "4) OpenConnect"
+ echo "5) Skip this step"
while true; do
read -r -p '' TUNNEL
case $TUNNEL in
1)
- opkg install sing-box
- break
- ;;
-
- 2)
opkg install wireguard-tools luci-proto-wireguard luci-app-wireguard
printf "\033[32;1mDo you want to configure the wireguard interface? (y/n): \033[0m\n"
@@ -130,7 +104,7 @@ add_tunnel() {
break
;;
- 3)
+ 2)
install_awg_packages
printf "\033[32;1mThere are no instructions for manual configure yet. Do you want to configure the amneziawg interface? (y/n): \033[0m\n"
@@ -143,19 +117,19 @@ add_tunnel() {
break
;;
- 4)
+ 3)
opkg install 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
;;
- 5)
+ 4)
opkg install 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
;;
- 6)
+ 5)
echo "Skip. Use this if you're installing an upgrade."
break
;;
@@ -395,8 +369,7 @@ check_system() {
# Check available space
AVAILABLE_SPACE=$(df /tmp | awk 'NR==2 {print $4}')
- # Change after switch sing-box
- REQUIRED_SPACE=1024 # 20MB in KB
+ REQUIRED_SPACE=15360 # 15MB in KB
echo "Available space: $((AVAILABLE_SPACE/1024))MB"
echo "Required space: $((REQUIRED_SPACE/1024))MB"
diff --git a/luci-app-podkop/Makefile b/luci-app-podkop/Makefile
index 56d7489..581167f 100644
--- a/luci-app-podkop/Makefile
+++ b/luci-app-podkop/Makefile
@@ -1,7 +1,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-podkop
-PKG_VERSION:=0.2.5
+PKG_VERSION:=0.3.0
PKG_RELEASE:=1
LUCI_TITLE:=LuCI podkop app
diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js
index 3326bdf..d4affbf 100644
--- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js
+++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js
@@ -18,8 +18,8 @@ return view.extend({
o = s.tab('basic', _('Basic Settings'));
o = s.taboption('basic', form.ListValue, 'mode', _('Connection Type'), _('Select between VPN and Proxy connection methods for traffic routing'));
- o.value('vpn', ('VPN'));
o.value('proxy', ('Proxy'));
+ o.value('vpn', ('VPN'));
o.ucisection = 'main';
o = s.taboption('basic', form.ListValue, 'proxy_config_type', _('Configuration Type'), _('Select how to configure the proxy'));
@@ -76,47 +76,33 @@ return view.extend({
console.error('Error fetching devices:', error);
}
- o = s.taboption('basic', form.Flag, 'domain_list_enabled', _('Community Domain Lists'));
+ o = s.taboption('basic', form.Flag, 'domain_list_enabled', _('Community Lists'));
o.default = '0';
o.rmempty = false;
o.ucisection = 'main';
- o = s.taboption('basic', form.ListValue, 'domain_list', _('Domain List'), _('Select a list') + ' github.com/itdoginfo/allow-domains');
- o.placeholder = 'placeholder';
- o.value('ru_inside', 'Russia inside');
- o.value('ru_outside', 'Russia outside');
- o.value('ua', 'Ukraine');
- o.depends('domain_list_enabled', '1');
- o.rmempty = false;
- o.ucisection = 'main';
-
- o = s.taboption('basic', form.Flag, 'delist_domains_enabled', _('Domain Exclusions'), _('Exclude specific domains from routing rules'));
- o.default = '0';
- o.rmempty = false;
- o.ucisection = 'main';
- o.depends('domain_list_enabled', '1');
-
- o = s.taboption('basic', form.DynamicList, 'delist_domains', _('Excluded Domains'), _('Domains to be excluded from routing'));
- o.placeholder = 'Delist domains';
- o.depends('delist_domains_enabled', '1');
- o.rmempty = false;
- o.ucisection = 'main';
-
- o = s.taboption('basic', form.Flag, 'subnets_list_enabled', _('Community Subnet Lists'), _('Enable routing for popular services like Twitter, Meta, and Discord'));
- o.default = '0';
- o.rmempty = false;
- o.ucisection = 'main';
-
- o = s.taboption('basic', form.DynamicList, 'subnets', _('Service Networks'), _('Select predefined service networks for routing'));
- o.placeholder = 'Service network list';
- o.value('twitter', 'Twitter(x.com)');
+ o = s.taboption('basic', form.DynamicList, 'domain_list', _('Service List'), _('Select predefined service for routing') + ' github.com/itdoginfo/allow-domains');
+ o.placeholder = 'Service list';
+ o.value('russia_inside', 'Russia inside');
+ o.value('russia_outside', 'Russia outside');
+ o.value('ukraine_inside', 'Ukraine');
+ o.value('geoblock', 'GEO Block');
+ o.value('block', 'Block');
+ o.value('porn', 'Porn');
+ o.value('news', 'News');
+ o.value('anime', 'Anime');
+ o.value('youtube', 'Youtube');
+ o.value('discord', 'Discord');
o.value('meta', 'Meta');
- o.value('discord', 'Discord(voice)');
- o.depends('subnets_list_enabled', '1');
+ o.value('twitter', 'Twitter (X)');
+ o.value('hdrezka', 'HDRezka');
+ o.value('tiktok', 'Tik-Tok');
+ o.value('telegram', 'Telegram');
+ o.depends('domain_list_enabled', '1');
o.rmempty = false;
o.ucisection = 'main';
- o = s.taboption('basic', form.ListValue, 'custom_domains_list_enabled', _('User Domain List Type'), _('Select how to add your custom domains'));
+ o = s.taboption('basic', form.ListValue, 'custom_domains_list_type', _('User Domain List Type'), _('Select how to add your custom domains'));
o.value('disabled', _('Disabled'));
o.value('dynamic', _('Dynamic List'));
o.value('text', _('Text List'));
@@ -126,7 +112,7 @@ return view.extend({
o = s.taboption('basic', form.DynamicList, 'custom_domains', _('User Domains'), _('Enter domain names without protocols (example: sub.example.com or example.com)'));
o.placeholder = 'Domains list';
- o.depends('custom_domains_list_enabled', 'dynamic');
+ o.depends('custom_domains_list_type', 'dynamic');
o.rmempty = false;
o.ucisection = 'main';
o.validate = function (section_id, value) {
@@ -144,7 +130,7 @@ return view.extend({
o = s.taboption('basic', form.TextValue, 'custom_domains_text', _('User Domains List'), _('Enter domain names separated by comma, space or newline (example: sub.example.com, example.com or one domain per line)'));
o.placeholder = 'example.com, sub.example.com\ndomain.com test.com\nsubdomain.domain.com another.com, third.com';
- o.depends('custom_domains_list_enabled', 'text');
+ o.depends('custom_domains_list_type', 'text');
o.rows = 10;
o.rmempty = false;
o.ucisection = 'main';
@@ -244,7 +230,6 @@ return view.extend({
return _('Invalid format. Use format: X.X.X.X or X.X.X.X/Y');
}
- // Разбираем IP и маску
const [ip, cidr] = value.split('/');
const ipParts = ip.split('.');
@@ -398,6 +383,11 @@ return view.extend({
return true;
};
+ o = s.taboption('basic', form.Flag, 'socks5', _('Mixed enable'), _('Browser port: 2080'));
+ o.default = '0';
+ o.rmempty = false;
+ o.ucisection = 'main';
+
// Additional Settings Tab
o = s.tab('additional', _('Additional Settings'));
@@ -408,12 +398,6 @@ return view.extend({
o.rmempty = false;
o.ucisection = 'main';
- o = s.taboption('additional', form.Flag, 'socks5', _('Mixed enable'), _('Browser port: 2080'));
- o.default = '0';
- o.depends('mode', 'proxy');
- o.rmempty = false;
- o.ucisection = 'main';
-
o = s.taboption('additional', form.Flag, 'exclude_ntp', _('Exclude NTP'), _('For issues with open connections sing-box'));
o.default = '0';
o.depends('mode', 'proxy');
@@ -421,238 +405,15 @@ return view.extend({
o.ucisection = 'main';
o = s.taboption('additional', form.ListValue, 'update_interval', _('List Update Frequency'), _('Select how often the lists will be updated'));
- o.value('0 */1 * * *', _('Every hour'));
- o.value('0 */2 * * *', _('Every 2 hours'));
- o.value('0 */4 * * *', _('Every 4 hours'));
- o.value('0 */6 * * *', _('Every 6 hours'));
- o.value('0 */12 * * *', _('Every 12 hours'));
- o.value('0 4 * * *', _('Once a day at 04:00'));
- o.value('0 4 * * 0', _('Once a week on Sunday at 04:00'));
- o.default = '0 4 * * *';
+ o.value('1h', _('Every hour'));
+ o.value('3h', _('Every 3 hours'));
+ o.value('12h', _('Every 12 hours'));
+ o.value('1d', _('Every day'));
+ o.value('3d', _('Every 3 days'));
+ o.default = '1d';
o.rmempty = false;
o.ucisection = 'main';
- // Secondary Settings Tab
-
- o = s.tab('secondary_config', _('Secondary Config'));
-
- o = s.taboption('secondary_config', form.Flag, 'second_enable', _('Secondary VPN/Proxy Enable'), _('Enable secondary VPN/Proxy configuration'));
- o.default = '0';
- o.rmempty = false;
- o.ucisection = 'second';
-
- o = s.taboption('secondary_config', form.ListValue, 'second_mode', _('Connection Type'), _('Select between VPN and Proxy connection methods for traffic routing'));
- o.value('vpn', ('VPN'));
- o.value('proxy', ('Proxy'));
- o.depends('second_enable', '1');
- o.ucisection = 'second';
-
- o = s.taboption('secondary_config', form.ListValue, 'second_proxy_config_type', _('Configuration Type'), _('Select how to configure the proxy'));
- o.value('url', _('Connection URL'));
- o.value('outbound', _('Outbound Config'));
- o.default = 'url';
- o.depends('second_mode', 'proxy');
- o.ucisection = 'second';
-
- o = s.taboption('secondary_config', form.TextValue, 'second_proxy_string', _('Proxy Configuration URL'), _('Enter connection string starting with vless:// or ss:// for proxy configuration'));
- o.depends('second_proxy_config_type', 'url');
- o.rows = 5;
- o.ucisection = 'second';
-
- o = s.taboption('secondary_config', form.TextValue, 'second_outbound_json', _('Outbound Configuration'), _('Enter complete outbound configuration in JSON format'));
- o.depends('second_proxy_config_type', 'outbound');
- o.rows = 10;
- o.ucisection = 'second';
- o.validate = function (section_id, value) {
- if (!value || value.length === 0) {
- return true;
- }
-
- try {
- const parsed = JSON.parse(value);
- if (!parsed.type || !parsed.server || !parsed.server_port) {
- return _('JSON must contain at least type, server and server_port fields');
- }
- return true;
- } catch (e) {
- return _('Invalid JSON format');
- }
- };
-
- o = s.taboption('secondary_config', form.ListValue, 'second_interface', _('Network Interface'), _('Select network interface for VPN connection'));
- o.depends('second_mode', 'vpn');
- o.ucisection = 'second';
-
- try {
- const devices = await network.getDevices();
- const excludeInterfaces = ['br-lan', 'eth0', 'eth1', 'wan', 'phy0-ap0', 'phy1-ap0'];
-
- devices.forEach(function (device) {
- if (device.dev && device.dev.name) {
- const deviceName = device.dev.name;
- const isExcluded = excludeInterfaces.includes(deviceName) || /^lan\d+$/.test(deviceName);
-
- if (!isExcluded) {
- o.value(deviceName, deviceName);
- }
- }
- });
- } catch (error) {
- console.error('Error fetching devices:', error);
- }
-
- o = s.taboption('secondary_config', form.Flag, 'second_domain_service_enabled', _('Service Domain List Enable'), _('Enable predefined service domain lists for routing'));
- o.default = '0';
- o.rmempty = false;
- o.depends('second_enable', '1');
- o.ucisection = 'second';
-
- o = s.taboption('secondary_config', form.ListValue, 'second_service_list', _('Service List'), _('Select predefined services for routing'));
- o.placeholder = 'placeholder';
- o.value('youtube', 'Youtube');
- o.depends('second_domain_service_enabled', '1');
- o.rmempty = false;
- o.ucisection = 'second';
-
- o = s.taboption('secondary_config', form.ListValue, 'second_custom_domains_list_enabled', _('User Domain List Type'), _('Select how to add your custom domains'));
- o.value('disabled', _('Disabled'));
- o.value('dynamic', _('Dynamic List'));
- o.value('text', _('Text List'));
- o.default = 'disabled';
- o.rmempty = false;
- o.depends('second_enable', '1');
- o.ucisection = 'second';
-
- o = s.taboption('secondary_config', form.DynamicList, 'second_custom_domains', _('User Domains'), _('Enter domain names without protocols (example: sub.example.com or example.com)'));
- o.placeholder = 'Domains list';
- o.depends('second_custom_domains_list_enabled', 'dynamic');
- o.rmempty = false;
- o.ucisection = 'second';
- o.validate = function (section_id, value) {
- if (!value || value.length === 0) {
- return true;
- }
-
- const domainRegex = /^(?!-)[A-Za-z0-9-]+([-.][A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
-
- if (!domainRegex.test(value)) {
- return _('Invalid domain format. Enter domain without protocol (example: sub.example.com)');
- }
- return true;
- };
-
- o = s.taboption('secondary_config', form.TextValue, 'second_custom_domains_text', _('User Domains List'), _('Enter domain names separated by comma, space or newline (example: sub.example.com, example.com or one domain per line)'));
- o.placeholder = 'example.com, sub.example.com\ndomain.com test.com\nsubdomain.domain.com another.com, third.com';
- o.depends('second_custom_domains_list_enabled', 'text');
- o.rows = 10;
- o.rmempty = false;
- o.ucisection = 'second';
- o.validate = function (section_id, value) {
- if (!value || value.length === 0) {
- return true;
- }
-
- const domains = value.split(/[,\s\n]/)
- .map(d => d.trim())
- .filter(d => d.length > 0);
-
- const domainRegex = /^(?!-)[A-Za-z0-9-]+([-.][A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
-
- for (const domain of domains) {
- if (!domainRegex.test(domain)) {
- return _('Invalid domain format: ' + domain + '. Enter domain without protocol');
- }
- }
- return true;
- };
-
- o = s.taboption('secondary_config', form.ListValue, 'second_custom_subnets_list_enabled', _('User Subnet List Type'), _('Select how to add your custom subnets'));
- o.value('disabled', _('Disabled'));
- o.value('dynamic', _('Dynamic List'));
- o.value('text', _('Text List'));
- o.default = 'disabled';
- o.rmempty = false;
- o.depends('second_enable', '1');
- o.ucisection = 'second';
-
- o = s.taboption('secondary_config', form.DynamicList, 'second_custom_subnets', _('User Subnets'), _('Enter subnets in CIDR notation (example: 103.21.244.0/22) or single IP addresses'));
- o.placeholder = 'IP or subnet';
- o.depends('second_custom_subnets_list_enabled', 'dynamic');
- o.rmempty = false;
- o.ucisection = 'second';
- o.validate = function (section_id, value) {
- if (!value || value.length === 0) {
- return true;
- }
-
- const subnetRegex = /^(\d{1,3}\.){3}\d{1,3}(\/\d{1,2})?$/;
-
- if (!subnetRegex.test(value)) {
- return _('Invalid format. Use format: X.X.X.X or X.X.X.X/Y');
- }
-
- const [ip, cidr] = value.split('/');
- const ipParts = ip.split('.');
-
- for (const part of ipParts) {
- const num = parseInt(part);
- if (num < 0 || num > 255) {
- return _('IP address parts must be between 0 and 255');
- }
- }
-
- if (cidr !== undefined) {
- const cidrNum = parseInt(cidr);
- if (cidrNum < 0 || cidrNum > 32) {
- return _('CIDR must be between 0 and 32');
- }
- }
-
- return true;
- };
-
- o = s.taboption('secondary_config', form.TextValue, 'second_custom_subnets_text', _('User Subnets List'), _('Enter subnets in CIDR notation or single IP addresses, separated by comma, space or newline'));
- o.placeholder = '103.21.244.0/22\n8.8.8.8\n1.1.1.1/32, 9.9.9.9 10.10.10.10';
- o.depends('second_custom_subnets_list_enabled', 'text');
- o.rows = 10;
- o.rmempty = false;
- o.ucisection = 'second';
- o.validate = function (section_id, value) {
- if (!value || value.length === 0) {
- return true;
- }
-
- const subnets = value.split(/[,\s\n]/)
- .map(s => s.trim())
- .filter(s => s.length > 0);
-
- const subnetRegex = /^(\d{1,3}\.){3}\d{1,3}(\/\d{1,2})?$/;
-
- for (const subnet of subnets) {
- if (!subnetRegex.test(subnet)) {
- return _('Invalid format: ' + subnet + '. Use format: X.X.X.X or X.X.X.X/Y');
- }
-
- const [ip, cidr] = subnet.split('/');
- const ipParts = ip.split('.');
-
- for (const part of ipParts) {
- const num = parseInt(part);
- if (num < 0 || num > 255) {
- return _('IP parts must be between 0 and 255 in: ' + subnet);
- }
- }
-
- if (cidr !== undefined) {
- const cidrNum = parseInt(cidr);
- if (cidrNum < 0 || cidrNum > 32) {
- return _('CIDR must be between 0 and 32 in: ' + subnet);
- }
- }
- }
- return true;
- };
-
o = s.tab('diagnostics', _('Diagnostics'));
function formatDiagnosticOutput(output) {
@@ -668,7 +429,7 @@ return view.extend({
`: ${status === 'available' ? '✓' : '✗'}`);
}
- // Check All - полная диагностика
+ // Check All - full diagnostic
o = s.taboption('diagnostics', form.Button, '_check_all');
o.title = _('Main Check');
o.description = _('Run a comprehensive diagnostic check of all components');
@@ -810,6 +571,333 @@ return view.extend({
]);
};
+
+ // Add new section 'extra'
+ var s = m.section(form.TypedSection, 'extra', _('Extra configurations'));
+ s.anonymous = false;
+ s.addremove = true;
+ s.addbtntitle = _('Add Section');
+
+ o = s.tab('basic', _('Extra configuration'));
+
+ o = s.taboption('basic', form.ListValue, 'mode', _('Connection Type'), _('Select between VPN and Proxy connection methods for traffic routing'));
+ o.value('proxy', ('Proxy'));
+ o.value('vpn', ('VPN'));
+
+ o = s.taboption('basic', form.ListValue, 'proxy_config_type', _('Configuration Type'), _('Select how to configure the proxy'));
+ o.value('url', _('Connection URL'));
+ o.value('outbound', _('Outbound Config'));
+ o.default = 'url';
+ o.depends('mode', 'proxy');
+
+ o = s.taboption('basic', form.TextValue, 'proxy_string', _('Proxy Configuration URL'), _('Enter connection string starting with vless:// or ss:// for proxy configuration'));
+ o.depends('proxy_config_type', 'url');
+ o.rows = 5;
+
+ o = s.taboption('basic', form.TextValue, 'outbound_json', _('Outbound Configuration'), _('Enter complete outbound configuration in JSON format'));
+ o.depends('proxy_config_type', 'outbound');
+ o.rows = 10;
+ o.validate = function (section_id, value) {
+ if (!value || value.length === 0) {
+ return true;
+ }
+
+ try {
+ const parsed = JSON.parse(value);
+ if (!parsed.type || !parsed.server || !parsed.server_port) {
+ return _('JSON must contain at least type, server and server_port fields');
+ }
+ return true;
+ } catch (e) {
+ return _('Invalid JSON format');
+ }
+ };
+
+ o = s.taboption('basic', form.ListValue, 'interface', _('Network Interface'), _('Select network interface for VPN connection'));
+ o.depends('mode', 'vpn');
+
+ try {
+ const devices = await network.getDevices();
+ const excludeInterfaces = ['br-lan', 'eth0', 'eth1', 'wan', 'phy0-ap0', 'phy1-ap0'];
+
+ devices.forEach(function (device) {
+ if (device.dev && device.dev.name) {
+ const deviceName = device.dev.name;
+ const isExcluded = excludeInterfaces.includes(deviceName) || /^lan\d+$/.test(deviceName);
+
+ if (!isExcluded) {
+ o.value(deviceName, deviceName);
+ }
+ }
+ });
+ } catch (error) {
+ console.error('Error fetching devices:', error);
+ }
+
+ o = s.taboption('basic', form.Flag, 'domain_list_enabled', _('Community Lists'));
+ o.default = '0';
+ o.rmempty = false;
+
+ o = s.taboption('basic', form.DynamicList, 'domain_list', _('Service List'), _('Select predefined service networks for routing') + ' github.com/itdoginfo/allow-domains');
+ o.placeholder = 'Service list';
+ o.value('russia_inside', 'Russia inside');
+ o.value('russia_outside', 'Russia outside');
+ o.value('ukraine_inside', 'Ukraine');
+ o.value('geoblock', 'GEO Block');
+ o.value('block', 'Block');
+ o.value('porn', 'Porn');
+ o.value('news', 'News');
+ o.value('anime', 'Anime');
+ o.value('youtube', 'Youtube');
+ o.value('discord', 'Discord');
+ o.value('meta', 'Meta');
+ o.value('twitter', 'Twitter (X)');
+ o.value('hdrezka', 'HDRezka');
+ o.value('tiktok', 'Tik-Tok');
+ o.value('telegram', 'Telegram');
+ o.depends('domain_list_enabled', '1');
+ o.rmempty = false;
+
+ o = s.taboption('basic', form.ListValue, 'custom_domains_list_type', _('User Domain List Type'), _('Select how to add your custom domains'));
+ o.value('disabled', _('Disabled'));
+ o.value('dynamic', _('Dynamic List'));
+ o.value('text', _('Text List'));
+ o.default = 'disabled';
+ o.rmempty = false;
+
+ o = s.taboption('basic', form.DynamicList, 'custom_domains', _('User Domains'), _('Enter domain names without protocols (example: sub.example.com or example.com)'));
+ o.placeholder = 'Domains list';
+ o.depends('custom_domains_list_type', 'dynamic');
+ o.rmempty = false;
+ o.validate = function (section_id, value) {
+ if (!value || value.length === 0) {
+ return true;
+ }
+
+ const domainRegex = /^(?!-)[A-Za-z0-9-]+([-.][A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
+
+ if (!domainRegex.test(value)) {
+ return _('Invalid domain format. Enter domain without protocol (example: sub.example.com)');
+ }
+ return true;
+ };
+
+ o = s.taboption('basic', form.TextValue, 'custom_domains_text', _('User Domains List'), _('Enter domain names separated by comma, space or newline (example: sub.example.com, example.com or one domain per line)'));
+ o.placeholder = 'example.com, sub.example.com\ndomain.com test.com\nsubdomain.domain.com another.com, third.com';
+ o.depends('custom_domains_list_type', 'text');
+ o.rows = 10;
+ o.rmempty = false;
+ o.validate = function (section_id, value) {
+ if (!value || value.length === 0) {
+ return true;
+ }
+
+ const domains = value.split(/[,\s\n]/)
+ .map(d => d.trim())
+ .filter(d => d.length > 0);
+
+ const domainRegex = /^(?!-)[A-Za-z0-9-]+([-.][A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
+
+ for (const domain of domains) {
+ if (!domainRegex.test(domain)) {
+ return _('Invalid domain format: ' + domain + '. Enter domain without protocol');
+ }
+ }
+ return true;
+ };
+
+ o = s.taboption('basic', form.Flag, 'custom_local_domains_list_enabled', _('Local Domain Lists'), _('Use the list from the router filesystem'));
+ o.default = '0';
+ o.rmempty = false;
+
+ o = s.taboption('basic', form.DynamicList, 'custom_local_domains', _('Local Domain Lists Path'), _('Enter to the list file path'));
+ o.placeholder = '/path/file.lst';
+ o.depends('custom_local_domains_list_enabled', '1');
+ o.rmempty = false;
+ o.validate = function (section_id, value) {
+ if (!value || value.length === 0) {
+ return true;
+ }
+
+ try {
+ const pathRegex = /^\/[a-zA-Z0-9_\-\/\.]+$/;
+ if (!pathRegex.test(value)) {
+ throw new Error(_('Invalid path format. Path must start with "/" and contain only valid characters (letters, numbers, "-", "_", "/", ".")'));
+ }
+ return true;
+ } catch (e) {
+ return _('Invalid path format');
+ }
+ };
+
+ o = s.taboption('basic', form.Flag, 'custom_download_domains_list_enabled', _('Remote Domain Lists'), _('Download and use domain lists from remote URLs'));
+ o.default = '0';
+ o.rmempty = false;
+
+ o = s.taboption('basic', form.DynamicList, 'custom_download_domains', _('Remote Domain URLs'), _('Enter full URLs starting with http:// or https://'));
+ o.placeholder = 'URL';
+ o.depends('custom_download_domains_list_enabled', '1');
+ o.rmempty = false;
+ o.validate = function (section_id, value) {
+ if (!value || value.length === 0) {
+ return true;
+ }
+
+ try {
+ const url = new URL(value);
+ if (!['http:', 'https:'].includes(url.protocol)) {
+ return _('URL must use http:// or https:// protocol');
+ }
+ return true;
+ } catch (e) {
+ return _('Invalid URL format. URL must start with http:// or https://');
+ }
+ };
+
+
+ o = s.taboption('basic', form.ListValue, 'custom_subnets_list_enabled', _('User Subnet List Type'), _('Select how to add your custom subnets'));
+ o.value('disabled', _('Disabled'));
+ o.value('dynamic', _('Dynamic List'));
+ o.value('text', _('Text List (comma/space/newline separated)'));
+ o.default = 'disabled';
+ o.rmempty = false;
+
+ o = s.taboption('basic', form.DynamicList, 'custom_subnets', _('User Subnets'), _('Enter subnets in CIDR notation (example: 103.21.244.0/22) or single IP addresses'));
+ o.placeholder = 'IP or subnet';
+ o.depends('custom_subnets_list_enabled', 'dynamic');
+ o.rmempty = false;
+ o.validate = function (section_id, value) {
+ if (!value || value.length === 0) {
+ return true;
+ }
+
+ const subnetRegex = /^(\d{1,3}\.){3}\d{1,3}(\/\d{1,2})?$/;
+
+ if (!subnetRegex.test(value)) {
+ return _('Invalid format. Use format: X.X.X.X or X.X.X.X/Y');
+ }
+
+ // Разбираем IP и маску
+ const [ip, cidr] = value.split('/');
+ const ipParts = ip.split('.');
+
+ for (const part of ipParts) {
+ const num = parseInt(part);
+ if (num < 0 || num > 255) {
+ return _('IP address parts must be between 0 and 255');
+ }
+ }
+
+ if (cidr !== undefined) {
+ const cidrNum = parseInt(cidr);
+ if (cidrNum < 0 || cidrNum > 32) {
+ return _('CIDR must be between 0 and 32');
+ }
+ }
+
+ return true;
+ };
+
+ o = s.taboption('basic', form.TextValue, 'custom_subnets_text', _('User Subnets List'), _('Enter subnets in CIDR notation or single IP addresses, separated by comma, space or newline'));
+ o.placeholder = '103.21.244.0/22\n8.8.8.8\n1.1.1.1/32, 9.9.9.9 10.10.10.10';
+ o.depends('custom_subnets_list_enabled', 'text');
+ o.rows = 10;
+ o.rmempty = false;
+ o.validate = function (section_id, value) {
+ if (!value || value.length === 0) {
+ return true;
+ }
+
+ // Split by commas, spaces and newlines
+ const subnets = value.split(/[,\s\n]/)
+ .map(s => s.trim())
+ .filter(s => s.length > 0);
+
+ const subnetRegex = /^(\d{1,3}\.){3}\d{1,3}(\/\d{1,2})?$/;
+
+ for (const subnet of subnets) {
+ if (!subnetRegex.test(subnet)) {
+ return _('Invalid format: ' + subnet + '. Use format: X.X.X.X or X.X.X.X/Y');
+ }
+
+ const [ip, cidr] = subnet.split('/');
+ const ipParts = ip.split('.');
+
+ for (const part of ipParts) {
+ const num = parseInt(part);
+ if (num < 0 || num > 255) {
+ return _('IP parts must be between 0 and 255 in: ' + subnet);
+ }
+ }
+
+ if (cidr !== undefined) {
+ const cidrNum = parseInt(cidr);
+ if (cidrNum < 0 || cidrNum > 32) {
+ return _('CIDR must be between 0 and 32 in: ' + subnet);
+ }
+ }
+ }
+ return true;
+ };
+
+ o = s.taboption('basic', form.Flag, 'custom_download_subnets_list_enabled', _('Remote Subnet Lists'), _('Download and use subnet lists from remote URLs'));
+ o.default = '0';
+ o.rmempty = false;
+
+ o = s.taboption('basic', form.DynamicList, 'custom_download_subnets', _('Remote Subnet URLs'), _('Enter full URLs starting with http:// or https://'));
+ o.placeholder = 'URL';
+ o.depends('custom_download_subnets_list_enabled', '1');
+ o.rmempty = false;
+ o.validate = function (section_id, value) {
+ if (!value || value.length === 0) {
+ return true;
+ }
+
+ try {
+ const url = new URL(value);
+ if (!['http:', 'https:'].includes(url.protocol)) {
+ return _('URL must use http:// or https:// protocol');
+ }
+ return true;
+ } catch (e) {
+ return _('Invalid URL format. URL must start with http:// or https://');
+ }
+ };
+
+ o = s.taboption('basic', form.Flag, 'all_traffic_from_ip_enabled', _('IP for full redirection'), _('Specify local IP addresses whose traffic will always use the configured route'));
+ o.default = '0';
+ o.rmempty = false;
+
+ o = s.taboption('basic', form.DynamicList, 'all_traffic_ip', _('Local IPs'), _('Enter valid IPv4 addresses'));
+ o.placeholder = 'IP';
+ o.depends('all_traffic_from_ip_enabled', '1');
+ o.rmempty = false;
+ o.validate = function (section_id, value) {
+ if (!value || value.length === 0) {
+ return true;
+ }
+
+ const ipRegex = /^(\d{1,3}\.){3}\d{1,3}$/;
+
+ if (!ipRegex.test(value)) {
+ return _('Invalid IP format. Use format: X.X.X.X (like 192.168.1.1)');
+ }
+
+ const ipParts = value.split('.');
+ for (const part of ipParts) {
+ const num = parseInt(part);
+ if (num < 0 || num > 255) {
+ return _('IP address parts must be between 0 and 255');
+ }
+ }
+
+ return true;
+ };
+
+ // For future
+ // o = s.taboption('basic', form.Flag, 'socks5', _('Mixed enable'), _('Browser port: 2080 (extra +1)'));
+ // o.default = '0';
+ // o.rmempty = false;
return m.render();
}
});
\ No newline at end of file
diff --git a/podkop/Makefile b/podkop/Makefile
index 6eb2fe7..e6b38ad 100644
--- a/podkop/Makefile
+++ b/podkop/Makefile
@@ -1,7 +1,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=podkop
-PKG_VERSION:=0.2.5
+PKG_VERSION:=0.3.0
PKG_RELEASE:=1
PKG_MAINTAINER:=ITDog
@@ -12,7 +12,7 @@ include $(INCLUDE_DIR)/package.mk
define Package/podkop
SECTION:=net
CATEGORY:=Network
- DEPENDS:=+dnsmasq-full +curl +jq +kmod-nft-tproxy +coreutils-base64
+ DEPENDS:=+sing-box +curl +jq +kmod-nft-tproxy +coreutils-base64
TITLE:=Domain routing app
URL:=https://itdog.info
PKGARCH:=all
@@ -33,8 +33,6 @@ define Package/podkop/prerm
grep -q "105 podkop" /etc/iproute2/rt_tables && sed -i "/105 podkop/d" /etc/iproute2/rt_tables
-rm -f /etc/hotplug.d/iface/50-podkop
-
exit 0
endef
@@ -49,9 +47,6 @@ define Package/podkop/install
$(INSTALL_DIR) $(1)/etc/config
$(INSTALL_CONF) ./files/etc/config/podkop $(1)/etc/config/podkop
-
- $(INSTALL_DIR) $(1)/etc/hotplug.d/iface
- $(INSTALL_DATA) ./files/etc/hotplug.d/iface/50-podkop $(1)/etc/hotplug.d/iface/50-podkop
endef
$(eval $(call BuildPackage,podkop))
diff --git a/podkop/files/etc/config/podkop b/podkop/files/etc/config/podkop
index 3268af8..0976aa8 100644
--- a/podkop/files/etc/config/podkop
+++ b/podkop/files/etc/config/podkop
@@ -1,13 +1,12 @@
config main 'main'
- option mode ''
- option interface ''
+ option mode 'proxy'
+ #option interface ''
option proxy_config_type ''
#option outbound_json ''
- #option proxy_string ''
+ option proxy_string ''
option domain_list_enabled '1'
- option domain_list 'ru_inside'
+ option domain_list 'russia_inside'
option subnets_list_enabled '0'
- #list subnets 'twitter'
option custom_domains_list_type 'disable'
#list custom_domains ''
#option custom_domains_text ''
@@ -29,21 +28,5 @@ config main 'main'
option yacd '0'
option socks5 '0'
option exclude_ntp '0'
- option update_interval ''
- option custom_domains_text
-
-config second 'second'
- option second_enable '0'
- option second_mode 'proxy'
- option second_interface ''
- option second_proxy_config_type ''
- #option second_outbound_json ''
- #option second_proxy_string ''
- option second_domain_service_enabled '0'
- #list second_service_list 'youtube'
- option second_custom_domains_type 'disable'
- #list second_custom_domains 'ifconfig.io'
- #option second_custom_domains_text ''
- option second_custom_subnets_type 'disable'
- #list second_custom_subnets ''
- #porion second_custom_subnets_text ''
\ No newline at end of file
+ option update_interval '1d'
+ option custom_domains_text
\ No newline at end of file
diff --git a/podkop/files/etc/hotplug.d/iface/50-podkop b/podkop/files/etc/hotplug.d/iface/50-podkop
deleted file mode 100755
index 8c7fa66..0000000
--- a/podkop/files/etc/hotplug.d/iface/50-podkop
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/sh
-
-. /lib/functions.sh
-config_load "/etc/config/podkop"
-. /etc/init.d/podkop
-
-config_get "interface" "main" "interface" "0"
-config_get "mode" "main" "mode" "0"
-if [ "$mode" = "vpn" ] && [ -n "$interface" ]; then
- add_route_interface "$interface" "podkop"
-fi
-
-if [ "$mode" = "proxy" ]; then
- echo "Add route for main tproxy"
- add_route_tproxy podkop
-fi
-
-config_get second_enable "second" "second_enable" "0"
-config_get second_interface "second" "second_interface" "0"
-config_get "second_mode" "second" "second_mode" "0"
-if [ "$second_enable" -eq "1" ] && [ "$second_mode" = "vpn" ] && [ -n "$second_interface" ]; then
- add_route_interface "$second_interface" "podkop2"
-fi
-
-if [ "$second_enable" -eq "1" ] && [ "$second_mode" = "proxy" ]; then
- echo "Add route for second tproxy"
- add_route_tproxy podkop2
-fi
\ No newline at end of file
diff --git a/podkop/files/etc/init.d/podkop b/podkop/files/etc/init.d/podkop
index 01a04c7..065af15 100755
--- a/podkop/files/etc/init.d/podkop
+++ b/podkop/files/etc/init.d/podkop
@@ -7,9 +7,8 @@ script=$(readlink "$initscript")
NAME="$(basename ${script:-$initscript})"
config_load "$NAME"
-EXTRA_COMMANDS="list_update add_route_interface check_proxy check_nft check_github check_logs check_all check_three"
+EXTRA_COMMANDS="list_update check_proxy check_nft check_github check_logs check_all check_three"
EXTRA_HELP=" list_update Updating domain and subnet lists
- add_route_interface Adding route for interface
sing_box_config_vless For test vless string
check_proxy Check if sing-box proxy works correctly
check_nft Show PodkopTable nftables rules
@@ -21,260 +20,64 @@ EXTRA_HELP=" list_update Updating domain and subnet lists
[ ! -L /usr/sbin/podkop ] && ln -s /etc/init.d/podkop /usr/sbin/podkop
GITHUB_RAW_URL="https://raw.githubusercontent.com/itdoginfo/allow-domains/main"
+SRS_MAIN_URL="https://github.com/itdoginfo/allow-domains/releases/latest/download"
DOMAINS_RU_INSIDE="${GITHUB_RAW_URL}/Russia/inside-dnsmasq-nfset.lst"
DOMAINS_RU_OUTSIDE="${GITHUB_RAW_URL}/Russia/outside-dnsmasq-nfset.lst"
DOMAINS_UA="${GITHUB_RAW_URL}/Ukraine/inside-dnsmasq-nfset.lst"
DOMAINS_YOUTUBE="${GITHUB_RAW_URL}/Services/youtube.lst"
-SUBNETS_TWITTER="${GITHUB_RAW_URL}/Subnets/IPv4/Twitter.lst"
-SUBNETS_META="${GITHUB_RAW_URL}/Subnets/IPv4/Meta.lst"
-SUBNETS_DISCORD="${GITHUB_RAW_URL}/Subnets/IPv4/Discord.lst"
+SUBNETS_TWITTER="${GITHUB_RAW_URL}/Subnets/IPv4/twitter.lst"
+SUBNETS_META="${GITHUB_RAW_URL}/Subnets/IPv4/meta.lst"
+SUBNETS_DISCORD="${GITHUB_RAW_URL}/Subnets/IPv4/discord.lst"
+SUBNETS_TELERAM="${GITHUB_RAW_URL}/Subnets/IPv4/telegram.lst"
SING_BOX_CONFIG="/etc/sing-box/config.json"
-
-config_get update_interval "main" "update_interval" "0 4 * * *"
-cron_job="${update_interval} /etc/init.d/podkop list_update"
+CACHE_FILE_PATH="/tmp/cache.db"
+FAKEIP="198.18.0.0/15"
start_service() {
log "Start podkop"
- dnsmasqfull
- routing_table_create
- add_mark
+ sing_box_version=$(sing-box version | head -n 1 | awk '{print $3}')
+ required_version="1.11.1"
- config_get mode "main" "mode"
- case "$mode" in
- "vpn")
- log "VPN mode"
- log "You are using VPN mode, make sure you have installed all the necessary packages, configured, created the zone and forwarding."
- config_get interface "main" "interface" "0"
- if [ -n "$interface" ]; then
- add_route_interface "$interface" "podkop"
- else
- log "Interface undefined"
- fi
-
- config_get_bool second_enable "second" "second_enable" "0"
- config_get second_mode "second" "second_mode" "0"
- if [ "$second_enable" -eq "1" ] && [ "$second_mode" = "proxy" ]; then
- config_get proxy_config_type "second" "second_proxy_config_type"
-
- if [ "$proxy_config_type" = "outbound" ]; then
- config_get outbound_json "second" "second_outbound_json"
- if [ -n "$outbound_json" ]; then
- log "Using JSON outbound configuration for second proxy"
- sing_box_config_outbound_json "$outbound_json" "1603"
- else
- log "Missing outbound JSON configuration"
- return
- fi
- else
- config_get proxy_string "second" "second_proxy_string"
- if [[ "$proxy_string" =~ ^ss:// ]]; then
- sing_box_config_shadowsocks "$proxy_string" "1603"
- elif [[ "$proxy_string" =~ ^vless:// ]]; then
- sing_box_config_vless "$proxy_string" "1603"
- else
- log "Unsupported proxy type or missing configuration"
- return
- fi
- fi
- add_route_tproxy podkop2
- sing_box_config_check
- sing_box_uci
- /etc/init.d/sing-box restart
- /etc/init.d/sing-box enable
- fi
-
- if [ "$second_enable" -eq "1" ] && [ "$second_mode" = "vpn" ]; then
- log "VPN mode for second"
- config_get interface "second" "second_interface" "0"
- if [ -n "$interface" ]; then
- add_route_interface "$interface" "podkop2"
- else
- log "Interface undefined"
- fi
- fi
- ;;
- "proxy")
- log "Proxy mode"
- if ! command -v sing-box >/dev/null 2>&1; then
- log "Sing-box isn't installed. Proxy mode works with sing-box"
- return
- fi
-
- # Main - proxy, Second - proxy
- config_get_bool second_enable "second" "second_enable" "0"
- config_get second_mode "second" "second_mode" "0"
- if [ "$second_enable" -eq "1" ] && [ "$second_mode" = "proxy" ]; then
- log "Two proxy enable"
- outbound_main=$(mktemp)
- outbound_second=$(mktemp)
-
- # Main proxy config
- config_get proxy_config_type main "proxy_config_type"
- if [ "$proxy_config_type" = "outbound" ]; then
- config_get outbound_json main "outbound_json"
- if [ -n "$outbound_json" ]; then
- echo '{"outbounds":[' > "$outbound_main"
- echo "$outbound_json" | jq '. + {tag: "main"}' >> "$outbound_main"
- echo ']}' >> "$outbound_main"
- else
- log "Missing main outbound JSON configuration"
- rm -f "$outbound_main" "$outbound_second"
- return
- fi
- else
- config_get proxy_string main "proxy_string"
- if [[ "$proxy_string" =~ ^ss:// ]]; then
- sing_box_config_shadowsocks "$proxy_string" "1602"
- jq '.outbounds[0] + {tag: "main"} | {outbounds: [.]}' $SING_BOX_CONFIG > "$outbound_main"
- elif [[ "$proxy_string" =~ ^vless:// ]]; then
- sing_box_config_vless "$proxy_string" "1602"
- jq '.outbounds[0] + {tag: "main"} | {outbounds: [.]}' $SING_BOX_CONFIG > "$outbound_main"
- else
- log "Unsupported proxy type or missing configuration for main"
- rm -f "$outbound_main" "$outbound_second"
- return
- fi
- fi
-
- # Second proxy config
- config_get proxy_config_type second "second_proxy_config_type"
- if [ "$proxy_config_type" = "outbound" ]; then
- config_get outbound_json second "second_outbound_json"
- if [ -n "$outbound_json" ]; then
- echo '{"outbounds":[' > "$outbound_second"
- echo "$outbound_json" | jq '. + {tag: "second"}' >> "$outbound_second"
- echo ']}' >> "$outbound_second"
- else
- log "Missing second outbound JSON configuration"
- rm -f "$outbound_main" "$outbound_second"
- return
- fi
- else
- config_get proxy_string "second" "second_proxy_string"
- if [[ "$proxy_string" =~ ^ss:// ]]; then
- sing_box_config_shadowsocks "$proxy_string" "1603"
- jq '.outbounds[0] + {tag: "second"} | {outbounds: [.]}' $SING_BOX_CONFIG > "$outbound_second"
- elif [[ "$proxy_string" =~ ^vless:// ]]; then
- sing_box_config_vless "$proxy_string" "1603"
- jq '.outbounds[0] + {tag: "second"} | {outbounds: [.]}' $SING_BOX_CONFIG > "$outbound_second"
- else
- log "Unsupported proxy type or missing configuration for second"
- rm -f "$outbound_main" "$outbound_second"
- return
- fi
- fi
-
- jq -s '{
- "log": {"level": "warn"},
- "inbounds": [
- {
- "type": "tproxy",
- "listen": "::",
- "listen_port": 1602,
- "sniff": false,
- "tag": "main"
- },
- {
- "type": "tproxy",
- "listen": "::",
- "listen_port": 1603,
- "sniff": false,
- "tag": "second"
- }
- ],
- "outbounds": (.[0].outbounds + .[1].outbounds),
- "route": {
- "rules": [
- {
- "inbound": "main",
- "outbound": "main"
- },
- {
- "inbound": "second",
- "outbound": "second"
- }
- ],
- "auto_detect_interface": true
- }
- }' "$outbound_main" "$outbound_second" > $SING_BOX_CONFIG
-
- rm -f "$outbound_main" "$outbound_second"
-
- add_route_tproxy podkop
- add_route_tproxy podkop2
- fi
-
- # Main proxy, second disable/vpn
- config_get_bool second_enable "second" "second_enable" "0"
- config_get second_mode "second" "second_mode" "0"
- if [ "$second_enable" -eq "0" ] || [ "$second_mode" = "vpn" ]; then
- config_get proxy_config_type main "proxy_config_type"
-
- if [ "$proxy_config_type" = "outbound" ]; then
- config_get outbound_json main "outbound_json"
- if [ -n "$outbound_json" ]; then
- log "Using JSON outbound configuration"
- sing_box_config_outbound_json "$outbound_json" "1602"
- else
- log "Missing outbound JSON configuration"
- return
- fi
- else
- config_get proxy_string main "proxy_string"
- if [[ "$proxy_string" =~ ^ss:// ]]; then
- sing_box_config_shadowsocks "$proxy_string" "1602"
- elif [[ "$proxy_string" =~ ^vless:// ]]; then
- sing_box_config_vless "$proxy_string" "1602"
- else
- log "Unsupported proxy type or missing configuration"
- return
- fi
- fi
- add_route_tproxy podkop
- fi
-
- sing_box_config_check
- sing_box_uci
- /etc/init.d/sing-box restart
- /etc/init.d/sing-box enable
-
- # Main proxy, Second VPN
- config_get_bool second_enable "second" "second_enable" "0"
- config_get second_mode "second" "second_mode" "0"
- if [ "$second_enable" -eq "1" ] && [ "$second_mode" = "vpn" ]; then
- log "VPN mode for seconds"
- log "You are using VPN mode, make sure you have installed all the necessary packages, configured, created the zone and forwarding."
- config_get interface "second" "second_interface" "0"
- if [ -n "$interface" ]; then
- add_route_interface "$interface" "podkop2"
- else
- log "Interface undefined"
- fi
- fi
- ;;
- *)
- log "Requires *vpn* or *proxy* value"
- return
- ;;
- esac
-
- list_update
-
- if [ "$domain_list_enabled" -eq 1 ] || [ "$subnets_list_enabled" -eq 1 ]; then
- add_cron_job
+ 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 install sing-box"
+ exit 1
fi
+
+ config_foreach wget_github
- config_get_bool all_traffic_from_ip_enabled "main" "all_traffic_from_ip_enabled" "0"
- if [ "$all_traffic_from_ip_enabled" -eq 1 ]; then
- log "Adding an IP to redirect all traffic"
- config_list_foreach main all_traffic_ip list_all_traffic_from_ip
- fi
+ mkdir -p /tmp/podkop
+ # base
+ route_table_rule_mark
+ create_nft_table
+ sing_box_uci
+
+ # sing-box
+ sing_box_inbound_proxy 1602
+ sing_box_dns
+ sing_box_dns_rule_fakeip
+ sing_box_rule_dns
+ sing_box_cache_file
+ process_socks5
+
+ # sing-box outbounds and rules
+ config_foreach sing_box_outdound
+ config_foreach process_domains_for_section
+ config_foreach process_remote_ruleset
+ config_foreach sing_box_rule_preset
+ config_foreach process_domains_list_local
+ config_foreach process_domains_list_url
+ config_foreach process_subnet_for_section
+ config_foreach process_subnet_for_section_remote
+ config_foreach process_all_traffic_for_section
+ config_foreach add_cron_job
+
+ #Future: exclude at the fakeip?
config_get_bool exclude_from_ip_enabled "main" "exclude_from_ip_enabled" "0"
if [ "$exclude_from_ip_enabled" -eq 1 ]; then
log "Adding an IP for exclusion"
- config_list_foreach main exclude_traffic_ip list_exclude_traffic_from_ip
+ config_list_foreach main exclude_traffic_ip sing_box_rules_source_ip_cidr $exclude_traffic_ip direct-out
fi
config_get_bool yacd "main" "yacd" "0"
@@ -284,19 +87,6 @@ start_service() {
"external_ui": "ui",
"external_controller": "0.0.0.0:9090"
}' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
- /etc/init.d/sing-box restart
- fi
-
- config_get_bool socks5 "main" "socks5" "0"
- if [ "$socks5" -eq 1 ]; then
- log "Socks5 local enable port 2080"
- jq '.inbounds += [{
- "type": "mixed",
- "listen": "0.0.0.0",
- "listen_port": 2080,
- "set_system_proxy": false
- }]' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
- /etc/init.d/sing-box restart
fi
config_get_bool exclude_ntp "main" "exclude_ntp" "0"
@@ -304,12 +94,25 @@ start_service() {
log "NTP traffic exclude for proxy"
nft insert rule inet PodkopTable mangle udp dport 123 return
fi
+
+ sing_box_config_check
+ /etc/init.d/sing-box restart
+ /etc/init.d/sing-box enable
+
+ config_get proxy_string "main" "proxy_string"
+ config_get interface "main" "interface"
+
+ if [ -n "$proxy_string" ] || [ -n "$interface" ]; then
+ dnsmasq_add
+ fi
}
stop_service() {
log "Stopping the podkop"
- rm -f /tmp/dnsmasq.d/podkop*
remove_cron_job
+ dnsmasq_rm
+
+ rm -rf /tmp/podkop/*
log "Flush nft"
if nft list table inet PodkopTable >/dev/null 2>&1; then
@@ -321,27 +124,15 @@ stop_service() {
ip rule del fwmark 0x105 table podkop priority 105
fi
- if ip rule list | grep -q "podkop2"; then
- ip rule del fwmark 0x106 table podkop2 priority 106
- fi
-
log "Flush ip route"
- if ip route list table podkop; then
+ if ip route list table podkop >/dev/null 2>&1; then
ip route flush table podkop
fi
- if ip route list table podkop2; then
- ip route flush table podkop2
- fi
-
log "Stop sing-box"
- config_get mode_main "main" "mode" "0"
- config_get mode_second "second" "second_mode" "0"
+ /etc/init.d/sing-box stop
+ /etc/init.d/sing-box disable
- if [ "$mode_main" = "proxy" ] || [ "$mode_second" = "proxy" ]; then
- /etc/init.d/sing-box stop
- /etc/init.d/sing-box disable
- fi
}
restart_service() {
@@ -380,13 +171,187 @@ nolog() {
echo -e "${CYAN}[$timestamp]${RESET} ${GREEN}$message${RESET}"
}
+# Main funcs
+
+route_table_rule_mark() {
+ local table=podkop
+
+ grep -q "105 $table" /etc/iproute2/rt_tables || echo "105 $table" >>/etc/iproute2/rt_tables
+
+ if ! ip route list table $table | grep -q "local default dev lo scope host"; then
+ log "Added route for tproxy"
+ ip route add local 0.0.0.0/0 dev lo table $table
+ else
+ log "Route for tproxy exists"
+ fi
+
+ if ! ip rule list | grep -q "from all fwmark 0x105 lookup $table"; then
+ log "Create marking rule"
+ ip -4 rule add fwmark 0x105 table $table priority 105
+ else
+ log "Marking rule exist"
+ fi
+}
+
+create_nft_table() {
+ local table="PodkopTable"
+
+ nft add table inet $table
+ log "Create nft rules"
+ 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 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 "br-lan" 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 "br-lan" 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 udp tproxy ip to :1602 counter
+}
+
+dnsmasq_add() {
+ ## Future: Check config and skip restart
+ log "Configure dnsmasq for sing-box"
+ uci set dhcp.@dnsmasq[0].noresolv="1"
+ uci set dhcp.@dnsmasq[0].filter_aaaa="1"
+ uci set dhcp.@dnsmasq[0].cachesize="0"
+ uci -q delete dhcp.@dnsmasq[0].server
+ uci add_list dhcp.@dnsmasq[0].server="127.0.0.1#5353"
+ uci add_list dhcp.@dnsmasq[0].server='/use-application-dns.net/'
+ uci commit dhcp
+
+ grep -q "filter-rr=HTTPS" /etc/dnsmasq.conf || echo "filter-rr=HTTPS" >> /etc/dnsmasq.conf
+
+ /etc/init.d/dnsmasq restart
+}
+
+dnsmasq_rm() {
+ log "Removing configuration for dnsmasq"
+ uci set dhcp.@dnsmasq[0].noresolv="0"
+ uci set dhcp.@dnsmasq[0].filter_aaaa="0"
+ uci set dhcp.@dnsmasq[0].cachesize="1000"
+ uci -q delete dhcp.@dnsmasq[0].server
+ uci commit dhcp
+
+ sed -i '/filter-rr=HTTPS/d' /etc/dnsmasq.conf
+
+ /etc/init.d/dnsmasq restart
+}
+
+process_domains_text() {
+ local text="$1"
+ local name="$2"
+
+ local tmp_file=$(mktemp)
+ echo "$text" > "$tmp_file"
+
+ sed 's/[, ]\+/\n/g' "$tmp_file" | while IFS= read -r domain; do
+ domain=$(echo "$domain" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
+ if [ -n "$domain" ]; then
+ sing_box_ruleset_domains "$domain" "$name"
+ fi
+ done
+
+ rm -f "$tmp_file"
+}
+
+process_subnets_text() {
+ local text="$1"
+ local name="$2"
+
+ local tmp_file=$(mktemp)
+ echo "$text" > "$tmp_file"
+
+ sed 's/[, ]\+/\n/g' "$tmp_file" | while IFS= read -r subnet; do
+ subnet=$(echo "$subnet" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
+ if [ -n "$subnet" ]; then
+ if ! echo "$subnet" | grep -q "/"; then
+ subnet="$subnet/32"
+ fi
+ sing_box_ruleset_subnets "$subnet" "$name"
+ fi
+ done
+
+ rm -f "$tmp_file"
+}
+
+wget_github() {
+ local count=0
+
+ config_get domain_list_enabled "$section" "domain_list_enabled"
+ config_get subnets_list_enabled "$section" "subnets_list_enabled"
+ config_get custom_download_domains_list_enabled "$section" "custom_download_domains_list_enabled"
+ config_get custom_download_subnets_list_enabled "$section" "custom_download_subnets_list_enabled"
+
+ if [ "$domain_list_enabled" -eq 1 ] || [ "$subnets_list_enabled" -eq 1 ] ||
+ [ "$custom_download_domains_list_enabled" -eq 1 ] || [ "$custom_download_subnets_list_enabled" -eq 1 ] ; then
+
+ while true; do
+ if ! curl -m 3 github.com; then
+ log "GitHub is not available. Check the internet availability [$count sec]"
+ count=$((count + 1))
+ else
+ return
+ fi
+
+ if [ $count -lt 30 ]; then
+ sleep_interval=1
+ elif [ $count -ge 30 ] && [ $count -lt 60 ]; then
+ sleep_interval=5
+ elif [ $count -ge 60 ] && [ $count -lt 90 ]; then
+ sleep_interval=10
+ else
+ sleep_interval=30
+ fi
+
+ sleep $sleep_interval
+ done
+ fi
+}
+
+
add_cron_job() {
- remove_cron_job
- crontab -l | {
- cat
- echo "$cron_job"
- } | crontab -
- log "The cron job has been created: $cron_job"
+ ## Future: make a check so that it doesn't recreate many times
+ config_get domain_list_enabled "$section" "domain_list_enabled"
+ config_get subnets_list_enabled "$section" "subnets_list_enabled"
+ config_get custom_download_domains_list_enabled "$section" "custom_download_domains_list_enabled"
+ config_get custom_download_subnets_list_enabled "$section" "custom_download_subnets_list_enabled"
+ config_get update_interval "main" "update_interval"
+
+ case "$update_interval" in
+ "1h")
+ cron_job="13 * * * * /etc/init.d/podkop list_update"
+ ;;
+ "3h")
+ cron_job="13 */3 * * * /etc/init.d/podkop list_update"
+ ;;
+ "12h")
+ cron_job="13 */12 * * * /etc/init.d/podkop list_update"
+ ;;
+ "1d")
+ cron_job="13 9 * * * /etc/init.d/podkop list_update"
+ ;;
+ "3d")
+ cron_job="13 9 */3 * * /etc/init.d/podkop list_update"
+ ;;
+ *)
+ log "Invalid update_interval value: $update_interval"
+ return
+ ;;
+ esac
+
+ if [ "$domain_list_enabled" -eq 1 ] || [ "$subnets_list_enabled" -eq 1 ] ||
+ [ "$custom_download_domains_list_enabled" -eq 1 ] || [ "$custom_download_subnets_list_enabled" -eq 1 ] ; then
+ remove_cron_job
+ crontab -l | {
+ cat
+ echo "$cron_job"
+ } | crontab -
+ log "The cron job has been created: $cron_job"
+ fi
}
remove_cron_job() {
@@ -395,506 +360,249 @@ remove_cron_job() {
}
list_update() {
- # Main domains processing
- config_get_bool domain_list_enabled "main" "domain_list_enabled" "0"
- if [ "$domain_list_enabled" -eq 1 ]; then
- log "Adding a common domains list"
- add_set "podkop_domains" "main"
- config_get domain_list main "domain_list"
- lists_domains_download "$domain_list"
- dnsmasq_config_check podkop-domains.lst
- fi
-
- # Main custom domains processing
- config_get custom_domains_list_type "main" "custom_domains_list_enabled" "disabled"
- if [ "$custom_domains_list_type" != "disabled" ]; then
- log "Adding a custom domains list"
- add_set "podkop_domains" "main"
- rm -f /tmp/dnsmasq.d/podkop-custom-domains.lst
-
- if [ "$custom_domains_list_type" = "dynamic" ]; then
- config_list_foreach main custom_domains "list_custom_domains_create" "podkop"
- elif [ "$custom_domains_list_type" = "text" ]; then
- config_get custom_domains_text main "custom_domains_text"
- process_domains_text "$custom_domains_text" "podkop"
- fi
-
- dnsmasq_config_check podkop-custom-domains.lst
- fi
-
- # Main custom download domains
- config_get_bool custom_download_domains_list_enabled "main" "custom_download_domains_list_enabled" "0"
- if [ "$custom_download_domains_list_enabled" -eq 1 ]; then
- log "Adding a custom domains list from URL"
- add_set "podkop_domains" "main"
- config_list_foreach main custom_download_domains "list_custom_download_domains_create" "podkop"
- fi
-
- # Main domains delist
- config_get_bool custom_local_domains_list_enabled "main" "custom_local_domains_list_enabled" "0"
- if [ "$custom_local_domains_list_enabled" -eq 1 ]; then
- log "Adding a custom local domain list"
- add_set "podkop_domains" "main"
- config_list_foreach main custom_local_domains "list_custom_local_domains_create" "podkop"
- fi
-
- config_get_bool delist_domains_enabled "main" "delist_domains_enabled" "0"
- if [ "$delist_domains_enabled" -eq 1 ] && [ "$domain_list_enabled" -eq 1 ]; then
- log "Exclude domains from the common list"
- config_list_foreach main delist_domains "list_delist_domains"
- dnsmasq_config_check podkop-domains.lst
- fi
-
- # Main subnets processing
- config_get_bool subnets_list_enabled "main" "subnets_list_enabled" "0"
- if [ "$subnets_list_enabled" -eq 1 ]; then
- log "Adding a subnets from list"
- mkdir -p /tmp/podkop
- add_set "podkop_subnets" "main"
- config_list_foreach main subnets "list_subnets_download"
- fi
-
- # Main custom subnets
- config_get custom_subnets_list_type "main" "custom_subnets_list_enabled" "disabled"
- if [ "$custom_subnets_list_type" != "disabled" ]; then
- log "Adding a custom subnets list"
- add_set "podkop_subnets" "main"
-
- if [ "$custom_subnets_list_type" = "dynamic" ]; then
- config_list_foreach main custom_subnets list_custom_subnets_preprocess "podkop"
- elif [ "$custom_subnets_list_type" = "text" ]; then
- config_get custom_subnets_text main "custom_subnets_text"
- process_subnets_text "$custom_subnets_text" "podkop"
- fi
- fi
-
- # Main custom download subnets
- config_get_bool custom_download_subnets_list_enabled "main" "custom_download_subnets_list_enabled" "0"
- if [ "$custom_download_subnets_list_enabled" -eq 1 ]; then
- log "Adding a subnets from URL"
- mkdir -p /tmp/podkop
- add_set "podkop_subnets" "main"
- config_list_foreach main custom_download_subnets "list_subnets_download"
- fi
-
- # Second custom domains processing
- config_get second_custom_domains_list_type "second" "second_custom_domains_list_enabled" "disabled"
- if [ "$second_custom_domains_list_type" != "disabled" ]; then
- log "Adding a custom domains list. Second podkop"
- add_set "podkop2_domains" "second"
- rm -f /tmp/dnsmasq.d/podkop2-custom-domains.lst
-
- if [ "$second_custom_domains_list_type" = "dynamic" ]; then
- config_list_foreach second second_custom_domains "list_custom_domains_create" "podkop2"
- elif [ "$second_custom_domains_list_type" = "text" ]; then
- config_get second_custom_domains_text second "second_custom_domains_text"
- process_domains_text "$second_custom_domains_text" "podkop2"
- fi
-
- dnsmasq_config_check podkop2-custom-domains.lst
- fi
-
- # Second service domains
- config_get_bool second_domain_service_enabled "second" "second_domain_service_enabled" "0"
- if [ "$second_domain_service_enabled" -eq 1 ]; then
- log "Adding a service for podkop2"
- add_set "podkop2_domains" "second"
- config_get second_service_list second "second_service_list"
- lists_services_download "$second_service_list"
- config_list_foreach second second_custom_domains "list_delist_domains"
- dnsmasq_config_check podkop2-domains.lst
- fi
-
- # Second custom subnets
- config_get second_custom_subnets_list_type "second" "second_custom_subnets_list_enabled" "disabled"
- if [ "$second_custom_subnets_list_type" != "disabled" ]; then
- log "Adding a custom subnets list. Second"
- add_set "podkop2_subnets" "second"
-
- if [ "$second_custom_subnets_list_type" = "dynamic" ]; then
- config_list_foreach second second_custom_subnets list_custom_subnets_preprocess "podkop2"
- elif [ "$second_custom_subnets_list_type" = "text" ]; then
- config_get second_custom_subnets_text second "second_custom_subnets_text"
- process_subnets_text "$second_custom_subnets_text" "podkop2"
- fi
- fi
-
- # Restart dnsmasq if needed
- if [ "$domain_list_enabled" -eq 1 ] || [ "$custom_domains_list_type" != "disabled" ] || \
- [ "$custom_download_domains_list_enabled" -eq 1 ] || \
- [ "$second_custom_domains_list_type" != "disabled" ] || \
- [ "$second_domain_service_enabled" -eq 1 ]; then
- /etc/init.d/dnsmasq restart
- fi
+ log "Update remote lists"
+ config_foreach process_remote_ruleset
+ config_foreach process_domains_list_url
+ config_foreach process_subnet_for_section_remote
}
-dnsmasqfull() {
- if /usr/sbin/dnsmasq -v | grep -q "no-nftset"; then
- log "Dnsmasq-full is not installed. Future: link only"
- log "Use script or:"
- log "cd /tmp/ && /bin/opkg download dnsmasq-full && /bin/opkg remove dnsmasq && /bin/opkg install dnsmasq-full --cache /tmp/ && cp /etc/config/dhcp /etc/config/dhcp-old && mv /etc/config/dhcp-opkg /etc/config/dhcp"
- return
- fi
-}
-routing_table_create() {
- grep -q "105 podkop" /etc/iproute2/rt_tables || echo '105 podkop' >>/etc/iproute2/rt_tables
- config_get_bool second_enable "second" "second_enable" "0"
- if [ "$second_enable" -eq 1 ]; then
- grep -q "106 podkop2" /etc/iproute2/rt_tables || echo '106 podkop2' >>/etc/iproute2/rt_tables
- fi
-}
+# sing-box funcs
-add_set() {
- local set_name="$1"
- local connect="$2"
-
- nft add table inet PodkopTable
- log "Create set $set_name"
- nft add chain inet PodkopTable mangle { type filter hook prerouting priority -150 \; policy accept \;}
- nft add set inet PodkopTable "$set_name" { type ipv4_addr\; flags interval\; auto-merge\; }
- if [ "$connect" = "main" ]; then
- config_get mode "$connect" "mode"
+sing_box_uci() {
+ local config="/etc/config/sing-box"
+ if grep -q "option enabled '0'" "$config" ||
+ grep -q "option user 'sing-box'" "$config"; then
+ sed -i \
+ -e "s/option enabled '0'/option enabled '1'/" \
+ -e "s/option user 'sing-box'/option user 'root'/" $config
+ log "Change sing-box UCI config"
else
- config_get mode "$connect" "second_mode"
+ log "Sing-box UCI config OK"
fi
+}
+
+# Future: for every section. +1 port?
+process_socks5() {
+ config_get_bool socks5 "main" "socks5" "0"
+ if [ "$socks5" -eq 1 ]; then
+ log "Socks5 local enable port 2080"
+ jq '.inbounds += [{
+ "tag": "mixed-in",
+ "type": "mixed",
+ "listen": "0.0.0.0",
+ "listen_port": 2080,
+ "set_system_proxy": false
+ }]' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
+
+ #local rule_exists=$(jq -r '.route.rules[] | select(.inbound[] == "mixed-in")' $SING_BOX_CONFIG)
+ local rule_exists=$(jq -r '.route.rules // [] | map(select(.inbound // [] | index("mixed-in"))) | length' $SING_BOX_CONFIG)
+
+ if [ -z "$rule_exists" ]; then
+ jq '.route.rules += [{
+ "inbound": ["mixed-in"],
+ "outbound": "main",
+ "action": "route"
+ }]' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
+ fi
+ fi
+}
+
+sing_box_inbound_proxy() {
+ local listen_port="$1"
+
+ jq -n \
+ --arg listen_port "$listen_port" \
+ '{
+ "log": {
+ "level": "warn"
+ },
+ "inbounds": [
+ {
+ "tag": "tproxy-in",
+ "type": "tproxy",
+ "listen": "::",
+ "listen_port": ($listen_port|tonumber),
+ "tcp_fast_open": true,
+ "udp_fragment": true
+ },
+ {
+ "tag": "dns-in",
+ "type": "direct",
+ "listen": "127.0.0.1",
+ "listen_port": 5353
+ }
+ ],
+ "outbounds": [
+ {
+ "tag": "direct-out",
+ "type": "direct"
+ }
+ ]
+ }' > $SING_BOX_CONFIG
+}
+
+sing_box_dns() {
+ log "Configure DNS in sing-box"
+ jq \
+ --arg FAKEIP "$FAKEIP" \
+ '.dns = {
+ "strategy": "ipv4_only",
+ "fakeip": {
+ "enabled": true,
+ "inet4_range": $FAKEIP
+ },
+ "servers": [
+ {
+ "tag": "cloudflare-doh-server",
+ "address": "https://1.1.1.1/dns-query",
+ "detour": "direct-out"
+ },
+ {
+ "tag": "fakeip-server",
+ "address": "fakeip"
+ }
+ ]
+ }' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
+}
+
+sing_box_dns_rule_fakeip() {
+ log "Configure fakeip route in sing-box"
+ jq \
+ '.dns += {
+ "rules": [
+ {
+ "server": "fakeip-server",
+ "rule_set": []
+ }
+ ]
+ }' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
+}
+
+sing_box_dns_rule_fakeip_section() {
+ local rule_set=$1
+
+ log "Adding section to fakeip route rules in sing-box"
+ jq \
+ --arg rule_set "$rule_set" \
+ '.dns.rules |= map(
+ if .server == "fakeip-server" then
+ .rule_set += [$rule_set]
+ else
+ .
+ end
+ )' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
+}
+
+
+sing_box_cache_file() {
+ log "Configure cache.db in sing-box"
+ jq \
+ --arg CACHE_FILE_PATH "$CACHE_FILE_PATH" \
+ '.experimental = {
+ "cache_file": {
+ "enabled": true,
+ "store_fakeip": true,
+ "path": $CACHE_FILE_PATH
+ }
+ }' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
+}
+
+sing_box_outdound() {
+ local section="$1"
+
+ config_get mode "$section" "mode"
case "$mode" in
"vpn")
- if ! nft list chain inet PodkopTable mangle | grep -q "ip daddr @"$set_name" meta mark set"; then
- if [ "$connect" = "main" ]; then
- nft add rule inet PodkopTable mangle iifname "br-lan" ip daddr @"$set_name" meta mark set 0x105 counter
- elif [ "$connect" = "second" ]; then
- nft add rule inet PodkopTable mangle iifname "br-lan" ip daddr @"$set_name" meta mark set 0x106 counter
- fi
- fi
+ log "VPN mode"
+ log "You are using VPN mode, make sure you have installed all the necessary packages, configured, created the zone and forwarding."
+ config_get interface "$section" "interface"
+ sing_box_outbound_interface $section $interface
;;
-
"proxy")
- nft add chain inet PodkopTable proxy { type filter hook prerouting priority -100 \; }
- if nft list table inet PodkopTable | grep -q "ip daddr @"$set_name" meta l4proto"; then
- log "Nft rule tproxy exists"
+ log "Proxy mode"
+ config_get proxy_config_type "$section" "proxy_config_type"
+
+ if [ "$proxy_config_type" = "outbound" ]; then
+ config_get outbound_json $section "outbound_json"
+ if [ -n "$outbound_json" ]; then
+ log "Using JSON outbound configuration"
+ sing_box_config_outbound_json "$outbound_json"
+ else
+ log "Missing outbound JSON configuration"
+ return
+ fi
else
- log "Added nft rule tproxy"
- if [ "$connect" = "main" ]; then
- nft add rule inet PodkopTable mangle iifname "br-lan" ip daddr @"$set_name" meta l4proto tcp meta mark set 0x105 counter
- nft add rule inet PodkopTable mangle iifname "br-lan" ip daddr @"$set_name" meta l4proto udp meta mark set 0x105 counter
- if ! ( nft list table inet PodkopTable | grep -q "meta mark 0x00000105 meta l4proto tcp tproxy" ); then
- nft add rule inet PodkopTable proxy meta mark 0x105 meta l4proto tcp tproxy ip to :1602 counter
- nft add rule inet PodkopTable proxy meta mark 0x105 meta l4proto udp tproxy ip to :1602 counter
- fi
- elif [ "$connect" = "second" ]; then
- nft add rule inet PodkopTable mangle iifname "br-lan" ip daddr @"$set_name" meta l4proto tcp meta mark set 0x106 counter
- nft add rule inet PodkopTable mangle iifname "br-lan" ip daddr @"$set_name" meta l4proto udp meta mark set 0x106 counter
- if ! ( nft list table inet PodkopTable | grep -q "meta mark 0x00000106 meta l4proto tcp tproxy" ); then
- nft add rule inet PodkopTable proxy meta mark 0x106 meta l4proto tcp tproxy ip to :1603 counter
- nft add rule inet PodkopTable proxy meta mark 0x106 meta l4proto udp tproxy ip to :1603 counter
- fi
+ config_get proxy_string $section "proxy_string"
+ if [[ "$proxy_string" =~ ^ss:// ]]; then
+ sing_box_config_shadowsocks "$section" "$proxy_string"
+ elif [[ "$proxy_string" =~ ^vless:// ]]; then
+ sing_box_config_vless "$section" "$proxy_string"
+ else
+ log "Unsupported proxy type or missing configuration"
+ return
fi
fi
;;
-
*)
log "Requires *vpn* or *proxy* value"
return
;;
- esac
+ esac
}
-add_route_interface() {
- local interface="$1"
- local table="$2"
- local retry_count_route=0
- local max_retries=10
+sing_box_outbound_interface() {
+ local section="$1"
+ local interface="$2"
- if ! ip link show "$interface" >/dev/null 2>&1; then
- log "Interface "$interface" undetected, wait 10 sec..."
- sleep 10
+ jq --arg section "$section" \
+ --arg interface "$interface" \
+ '. |
+ .outbounds |= (
+ map(
+ if .tag == $section then
+ . + {"type": "direct", "bind_interface": $interface}
+ else . end
+ ) +
+ (
+ if (map(select(.tag == $section)) | length) == 0 then
+ [{"tag": $section, "type": "direct", "bind_interface": $interface}]
+ else [] end
+ )
+ )' "$SING_BOX_CONFIG" > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$SING_BOX_CONFIG"
- if ! ip link show "$interface" >/dev/null 2>&1; then
- log "Interface "$interface" undetected. exit"
- return
- fi
- fi
-
- if ! ip link show "$interface" >/dev/null 2>&1; then
- log "Interface "$interface" does not exist, not possible to create a route"
- return
- fi
-
- if ip route show table $table | grep -q "^default dev"; then
- log "Route for "$interface" exists"
- return 0
- fi
-
- log "Added route for "$interface""
- while [ $retry_count_route -lt $max_retries ]; do
- if ip route add table $table default dev "$interface" 2>&1 | grep -q "Network is down"; then
- log "Attempt $retry_count_route: Interface "$interface" is down, retrying in 3 seconds..."
- sleep 3
- retry_count_route=$((retry_count_route + 1))
- else
- log "Route for "$interface" added"
- return 0
- fi
- done
-
- log "The maximum number of attempts has been exceeded. Failed to add a route."
- return
-}
-
-add_route_tproxy() {
- local table=$1
- if ! ip route list table $table | grep -q "local default dev lo scope host"; then
- log "Added route for tproxy"
- ip route add local 0.0.0.0/0 dev lo table $table
+ if [ $? -eq 0 ]; then
+ log "Config updated successfully"
else
- log "Route for tproxy exists"
+ log "Error: Invalid JSON config generated"
+ return 1
fi
}
-add_mark() {
- if ! ip rule list | grep -q "from all fwmark 0x105 lookup podkop"; then
- log "Create marking rule"
- ip -4 rule add fwmark 0x105 table podkop priority 105
- else
- log "Marking rule exist"
- fi
-
- config_get_bool second_enable "second" "second_enable" "0"
- if [ "$second_enable" -eq 1 ]; then
- if ! ip rule list | grep -q "from all fwmark 0x106 lookup podkop2"; then
- log "Create marking rule for podkop second"
- ip -4 rule add fwmark 0x106 table podkop2 priority 106
- else
- log "Podkop second marking rule exist"
- fi
- fi
+sing_box_rule_dns() {
+ log "Configure rule dns in sing-box"
+ jq \
+ '.route += {
+ "rules": [
+ {
+ "inbound": [
+ "dns-in",
+ "tproxy-in"
+ ],
+ "action": "sniff"
+ },
+ {
+ "protocol": "dns",
+ "action": "hijack-dns"
+ }
+ ],
+ "auto_detect_interface": true
+ }' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
}
-lists_domains_download() {
- local URL="$1"
-
- case "$URL" in
- "ru_inside")
- URL=$DOMAINS_RU_INSIDE
- ;;
- "ru_outside")
- URL=$DOMAINS_RU_OUTSIDE
- ;;
- "ua")
- URL=$DOMAINS_UA
- ;;
- *)
- log "Unidentified list of domains"
- return
- ;;
- esac
-
- count=0
- while true; do
- if curl -m 3 github.com; then
- wget -q -O /tmp/dnsmasq.d/podkop-domains.lst $URL
- if [ $? -eq 0 ]; then
- sed -i 's/fw4#vpn_domains/PodkopTable#podkop_domains/g' /tmp/dnsmasq.d/podkop-domains.lst
- return 0
- fi
- else
- log "GitHub is not available. Check the internet availability [$count sec]"
- count=$((count + 1))
- fi
-
- if [ $count -lt 30 ]; then
- sleep_interval=1
- elif [ $count -ge 30 ] && [ $count -lt 60 ]; then
- sleep_interval=5
- elif [ $count -ge 60 ] && [ $count -lt 90 ]; then
- sleep_interval=10
- else
- sleep_interval=30
- fi
-
- sleep $sleep_interval
- done
-}
-
-lists_services_download() {
- local URL="$1"
-
- case "$URL" in
- "youtube")
- URL=$DOMAINS_YOUTUBE
- ;;
- *)
- log "Unidentified list of domains"
- return
- ;;
- esac
-
- count=0
- while true; do
- if ping -c 1 -W 3 github.com >/dev/null 2>&1; then
- wget -q -O /tmp/dnsmasq.d/podkop2-domains.lst $URL
- if [ $? -eq 0 ]; then
- delist_downloaded_domains
- sed -i 's/.*/nftset=\/&\/4#inet#PodkopTable#podkop2_domains/g' /tmp/dnsmasq.d/podkop2-domains.lst
- return 0
- fi
- else
- log "GitHub is not available. Check the internet availability [$count sec]"
- count=$((count + 1))
- fi
-
- if [ $count -lt 30 ]; then
- sleep_interval=1
- elif [ $count -ge 30 ] && [ $count -lt 60 ]; then
- sleep_interval=5
- elif [ $count -ge 60 ] && [ $count -lt 90 ]; then
- sleep_interval=10
- else
- sleep_interval=30
- fi
-
- sleep $sleep_interval
- done
-}
-
-list_subnets_download() {
- local URL="$1"
-
- case "$URL" in
- "twitter")
- URL=$SUBNETS_TWITTER
- ;;
- "meta")
- URL=$SUBNETS_META
- ;;
- "discord")
- URL=$SUBNETS_DISCORD
- ;;
- *)
- log "Custom URL for subnet"
- if wget -q --spider "$URL"; then
- log "URL is valid"
- else
- log "URL $URL is not valid"
- return
- fi
- ;;
- esac
-
- local filename=$(basename "$URL")
- mkdir -p /tmp/podkop
- wget -q -O "/tmp/podkop/$filename" "$URL"
- while IFS= read -r subnet; do
- nft add element inet PodkopTable podkop_subnets { $subnet }
- done <"/tmp/podkop/$filename"
-}
-
-list_custom_domains_create() {
- local domain="$1"
- local name="$2"
- echo "nftset=/$domain/4#inet#PodkopTable#${name}_domains" >>"/tmp/dnsmasq.d/${name}-custom-domains.lst"
- log "$domain added to the list"
-}
-
-list_custom_local_domains_create() {
- local local_file="$1"
- local name="$2"
- local filename=$(basename "$local_file" | cut -d. -f1)
- local config="/tmp/dnsmasq.d/${name}-${filename}.lst"
-
- rm -f "$config"
- while IFS= read -r domain; do
- echo "nftset=/$domain/4#inet#PodkopTable#${name}_domains" >>$config
- done <"$local_file"
- dnsmasq_config_check ${name}-${filename}.lst
-}
-
-list_custom_download_domains_create() {
- local URL="$1"
- local name="$2"
- local filename=$(basename "$URL")
- local config="/tmp/dnsmasq.d/${name}-${filename}.lst"
-
- rm -f "$config"
- mkdir -p /tmp/podkop
- wget -q -O "/tmp/podkop/${filename}" "$URL"
- while IFS= read -r domain; do
- echo "nftset=/$domain/4#inet#PodkopTable#${name}_domains" >>$config
- done <"/tmp/podkop/$filename"
- dnsmasq_config_check ${name}-${filename}.lst
-}
-
-list_custom_subnets_create() {
- local subnet="$1"
- local name="$2"
- nft add element inet PodkopTable ${name}_subnets { $subnet }
-}
-
-list_all_traffic_from_ip() {
- local ip="$1"
- if ! nft list chain inet PodkopTable mangle | grep -q "ip saddr $ip"; then
- config_get mode "main" "mode" "0"
- if [ "$mode" = "vpn" ]; then
- nft insert rule inet PodkopTable mangle iifname "br-lan" ip saddr $ip meta mark set 0x105 counter
- elif [ "$mode" = "proxy" ]; then
- nft add set inet PodkopTable localv4 { type ipv4_addr\; flags interval\; }
- nft add element inet PodkopTable localv4 { \
- 0.0.0.0/8, \
- 10.0.0.0/8, \
- 127.0.0.0/8, \
- 169.254.0.0/16, \
- 172.16.0.0/12, \
- 192.0.0.0/24, \
- 192.0.2.0/24, \
- 192.88.99.0/24, \
- 192.168.0.0/16, \
- 198.18.0.0/15, \
- 198.51.100.0/24, \
- 203.0.113.0/24, \
- 224.0.0.0/4, \
- 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 PodkopTable mangle ip saddr $ip ip daddr @localv4 return
- fi
- fi
-}
-
-list_exclude_traffic_from_ip() {
- local ip="$1"
- if ! nft list chain inet PodkopTable mangle | grep -q "ip saddr $ip"; then
- nft insert rule inet PodkopTable mangle ip saddr $ip return
- fi
-}
-
-list_delist_domains() {
- local domain="$1"
-
- if [ -f "/tmp/dnsmasq.d/podkop-domains.lst" ]; then
- sed -i "/$domain/d" /tmp/dnsmasq.d/podkop-domains.lst
- nft flush set inet PodkopTable podkop_domains
- log "Strings containing '$domain' have been excluded from the list"
- else
- log "Config /tmp/dnsmasq.d/podkop-domains.lst not exists"
- fi
-}
-
-delist_downloaded_domains() {
- local domains="/tmp/dnsmasq.d/podkop2-domains.lst"
-
- if [ -f "$domains" ]; then
- while IFS= read -r line; do
- list_delist_domains "$line"
- done <"$domains"
- else
- log "$domains not found"
- fi
-}
-
-dnsmasq_config_check() {
- local config="$1"
- if ! /usr/sbin/dnsmasq --conf-file=/tmp/dnsmasq.d/$config --test 2>&1 | grep -q "syntax check OK"; then
- log "Dnsmasq config $config contains errors. Break"
+sing_box_config_check() {
+ if ! sing-box -c $SING_BOX_CONFIG check >/dev/null 2>&1; then
+ log "Sing-box configuration is invalid"
return
fi
}
@@ -910,11 +618,18 @@ sing_box_config_outbound_json() {
},
"inbounds": [
{
- "type": "tproxy",
- "listen": "::",
- "listen_port": $listen_port,
- "sniff": false
- }
+ "type": "tproxy",
+ "listen": "::",
+ "listen_port": $listen_port,
+ "tcp_fast_open": true,
+ "udp_fragment": true
+ },
+ {
+ "tag": "dns-in",
+ "type": "direct",
+ "listen": "127.0.0.1",
+ "listen_port": 5353
+ }
],
"outbounds": [],
"route": {
@@ -927,37 +642,21 @@ EOF
rm -f /tmp/base_config.json
}
-sing_box_uci() {
- local config="/etc/config/sing-box"
- if grep -q "option enabled '0'" "$config" ||
- grep -q "option user 'sing-box'" "$config"; then
- sed -i \
- -e "s/option enabled '0'/option enabled '1'/" \
- -e "s/option user 'sing-box'/option user 'root'/" $config
- log "Change sing-box UCI config"
- else
- log "Sing-box UCI config OK"
- fi
-}
+
sing_box_config_shadowsocks() {
- local STRING="$1"
- local listen_port="$2"
+ local section="$1"
+ local STRING="$2"
- # Определяем тип SS (2022 или old) по наличию : в base64 части
if echo "$STRING" | cut -d'/' -f3 | cut -d'@' -f1 | base64 -d 2>/dev/null | grep -q ":"; then
- # Old SS format
- local encrypted_part=$(echo "$STRING" | cut -d'/' -f3 | cut -d'@' -f1 | base64 --decode)
+ local encrypted_part=$(echo "$STRING" | cut -d'/' -f3 | cut -d'@' -f1 | base64 -d 2>/dev/null )
local method=$(echo "$encrypted_part" | cut -d':' -f1)
local password=$(echo "$encrypted_part" | cut -d':' -f2-)
else
- # SS 2022 format
local method_and_password=$(echo "$STRING" | cut -d'/' -f3 | cut -d'@' -f1)
local method=$(echo "$method_and_password" | cut -d':' -f1)
local password=$(echo "$method_and_password" | cut -d':' -f2- | sed 's/%3D/=/g')
-
- # Если method в base64, декодируем
- if echo "$method" | base64 -d &>/dev/null; then
+ if echo "$method" | base64 -d ; then
method=$(echo "$method" | base64 -d)
fi
fi
@@ -965,45 +664,52 @@ sing_box_config_shadowsocks() {
local server=$(echo "$STRING" | cut -d'@' -f2 | cut -d':' -f1)
local port=$(echo "$STRING" | sed -n 's|.*:\([0-9]\+\).*|\1|p')
- # Create base config
- cat > /tmp/ss_config.json << EOF
-{
- "log": {
- "level": "warn"
- },
- "inbounds": [
- {
- "type": "tproxy",
- "listen": "::",
- "listen_port": $listen_port,
- "sniff": false
- }
- ],
- "outbounds": [
- {
- "type": "shadowsocks",
- "server": "$server",
- "server_port": $port,
- "method": "$method",
- "password": "$password",
- "udp_over_tcp": {
- "enabled": true,
- "version": 2
- }
- }
- ],
- "route": {
- "auto_detect_interface": true
- }
-}
-EOF
+ jq \
+ --arg section "$section" \
+ --arg server "$server" \
+ --argjson port "$port" \
+ --arg method "$method" \
+ --arg password "$password" \
+ '. |
+ .outbounds |= (
+ map(
+ if .tag == $section then
+ . + {
+ "type": "shadowsocks",
+ "server": $server,
+ "server_port": ($port | tonumber),
+ "method": $method,
+ "password": $password,
+ "udp_over_tcp": { "enabled": true, "version": 2 }
+ }
+ else . end
+ ) +
+ (
+ if (map(select(.tag == $section)) | length) == 0 then
+ [{
+ "tag": $section,
+ "type": "shadowsocks",
+ "server": $server,
+ "server_port": ($port | tonumber),
+ "method": $method,
+ "password": $password,
+ "udp_over_tcp": { "enabled": true, "version": 2 }
+ }]
+ else [] end
+ )
+ )' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
- mv /tmp/ss_config.json $SING_BOX_CONFIG
+ if [ $? -eq 0 ]; then
+ log "Config updated successfully"
+ else
+ log "Error: Invalid JSON config generated"
+ return 1
+ fi
}
sing_box_config_vless() {
- local STRING="$1"
- local listen_port="$2"
+ local section="$1"
+ local STRING="$2"
get_param() {
local param="$1"
@@ -1016,8 +722,7 @@ sing_box_config_vless() {
server=$(echo "$STRING" | cut -d'@' -f2 | cut -d':' -f1 | tr -d '\n' | tr -d '\r' | sed 's/False//g')
port=$(echo "$STRING" | cut -d'@' -f2 | cut -d':' -f2 | cut -d'?' -f1 | cut -d'/' -f1 | cut -d'#' -f1 | tr -d '\n' | tr -d '\r' | sed 's/False//g')
- jq -n \
- --arg listen_port "$listen_port" \
+ jq \
--arg server "$server" \
--argjson port "$port" \
--arg uuid "$uuid" \
@@ -1033,88 +738,483 @@ sing_box_config_vless() {
--arg host "$(get_param "host")" \
--arg spx "$(get_param "spx")" \
--arg insecure "$(get_param "allowInsecure")" \
- '{
- "log": {
- "level": "warn"
- },
- "inbounds": [
- {
- "type": "tproxy",
- "listen": "::",
- "listen_port": ($listen_port|tonumber),
- "sniff": false
+ --arg section "$section" \
+ '. |
+ # Updating an existing outbound by tag or adding a new one
+ .outbounds |= (
+ # If an element with the required tag is found, update it
+ map(
+ if .tag == $section then
+ . + {
+ "type": "vless",
+ "server": $server,
+ "server_port": ($port | tonumber),
+ "uuid": $uuid,
+ "packet_encoding": "",
+ "domain_strategy": "",
+ "flow": $flow
}
- ],
- "outbounds": [
- {
- "type": "vless",
- "server": $server,
- "server_port": ($port|tonumber),
- "uuid": $uuid,
- "packet_encoding": "",
- "domain_strategy": ""
- }
- ],
- "route": {
- "auto_detect_interface": true
- }
- } |
-
- if $flow != "" then .outbounds[0].flow = $flow else . end |
-
- if $type == "ws" then
- .outbounds[0].transport = {
- "type": "ws",
- "path": $path
- } |
- if $host != "" then
- .outbounds[0].transport.headers = {
- "Host": $host
- }
- else . end
- elif $type == "grpc" then
- .outbounds[0].transport = {
- "type": "grpc"
- }
- else . end |
-
- if $security == "reality" or $security == "tls" then
- .outbounds[0].tls = {
- "enabled": true,
- "server_name": $sni,
- "utls": {
+ else . end
+ ) +
+ # Add a new outbound if the required tag is not present
+ (
+ if (map(select(.tag == $section)) | length) == 0 then
+ [{
+ "tag": $section,
+ "type": "vless",
+ "server": $server,
+ "server_port": ($port | tonumber),
+ "uuid": $uuid,
+ "packet_encoding": "",
+ "domain_strategy": "",
+ "flow": $flow
+ }]
+ else [] end
+ )
+ ) |
+ # Additional parameters such as transport and tls
+ if $flow != "" then
+ .outbounds |= map(
+ if .tag == $section then
+ .flow = $flow
+ else . end
+ )
+ else . end |
+ if $type == "ws" then
+ .outbounds |= map(
+ if .tag == $section then
+ .transport = {
+ "type": "ws",
+ "path": $path
+ } |
+ if $host != "" then
+ .transport.headers = { "Host": $host }
+ else . end
+ else . end
+ )
+ elif $type == "grpc" then
+ .outbounds |= map(
+ if .tag == $section then
+ .transport = { "type": "grpc" }
+ else . end
+ )
+ else . end |
+ if $security == "reality" or $security == "tls" then
+ .outbounds |= map(
+ if .tag == $section then
+ .tls = {
+ "enabled": true,
+ "server_name": $sni,
+ "utls": {
"enabled": true,
"fingerprint": $fp
- },
- "insecure": ($insecure == "1")
- } |
- if $alpn != "" then
- .outbounds[0].tls.alpn = ($alpn | split(","))
- else . end |
- if $security == "reality" then
- .outbounds[0].tls.reality = {
+ },
+ "insecure": ($insecure == "1")
+ } |
+ if $alpn != "" then
+ .tls.alpn = ($alpn | split(","))
+ else . end |
+ if $security == "reality" then
+ .tls.reality = {
"enabled": true,
"public_key": $pbk,
"short_id": $sid
- }
- else . end
- else . end' > $SING_BOX_CONFIG
+ }
+ 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
+
if [ $? -eq 0 ]; then
- echo "Config created successfully"
+ log "Config created successfully"
else
- echo "Error: Invalid JSON config generated"
+ log "Error: Invalid JSON config generated"
return 1
fi
}
-sing_box_config_check() {
- if ! sing-box -c $SING_BOX_CONFIG check >/dev/null 2>&1; then
- log "Sing-box configuration is invalid"
- return
+# Process. Sing-box rules
+
+sing_box_ruleset_domains() {
+ log "Configure ruleset domains in sing-box"
+
+ local domain=$1
+ local tag=$2
+
+ # Check if there is a route.rule_set for the specified tag
+ local tag_exists=$(jq -r --arg tag "$tag" '
+ .route.rule_set[]? | select(.tag == $tag) | .tag
+ ' /etc/sing-box/config.json)
+
+ # If the tag exists, add the domain
+ if [[ -n "$tag_exists" ]]; then
+ jq \
+ --arg tag "$tag" \
+ --arg domain "$domain" \
+ '
+ .route.rule_set[] |=
+ if .tag == $tag then
+ .rules[0].domain_suffix += [$domain]
+ else
+ .
+ end
+ ' /etc/sing-box/config.json > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json /etc/sing-box/config.json
+
+ log "$domain added to the list for tag $tag"
+ else
+ # If tag does not exist, add a new set of rules
+ jq \
+ --arg tag "$tag" \
+ --arg domain "$domain" \
+ '
+ .route.rule_set += [
+ {
+ "tag": $tag,
+ "type": "inline",
+ "rules": [
+ {
+ "domain_suffix": [$domain]
+ }
+ ]
+ }
+ ]' /etc/sing-box/config.json > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json /etc/sing-box/config.json
+
+ log "$domain added as a new rule set for tag $tag"
fi
}
+sing_box_ruleset_subnets() {
+ log "Configure ruleset domains in sing-box"
+
+ local subnet=$1
+ local tag=$2
+
+ # nft
+ nft add element inet PodkopTable podkop_subnets { $subnet }
+
+ # Check if there is a route.rule_set for the specified tag
+ local tag_exists=$(jq -r --arg tag "$tag" '
+ .route.rule_set[]? | select(.tag == $tag) | .tag
+ ' /etc/sing-box/config.json)
+
+ # If tag exists, add the domain
+ if [[ -n "$tag_exists" ]]; then
+ jq \
+ --arg tag "$tag" \
+ --arg subnet "$subnet" \
+ '
+ .route.rule_set[] |=
+ if .tag == $tag then
+ .rules[0].ip_cidr += [$subnet]
+ else
+ .
+ end
+ ' /etc/sing-box/config.json > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json /etc/sing-box/config.json
+
+ log "$subnet added to the list for tag $tag"
+ else
+ # If tag does not exist, add a new set of rules
+ jq \
+ --arg tag "$tag" \
+ --arg subnet "$subnet" \
+ '
+ .route.rule_set += [
+ {
+ "tag": $tag,
+ "type": "inline",
+ "rules": [
+ {
+ "ip_cidr": [$subnet]
+ }
+ ]
+ }
+ ]' /etc/sing-box/config.json > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json /etc/sing-box/config.json
+
+ log "$subnet added as a new rule set for tag $tag"
+ fi
+}
+
+process_domains_for_section() {
+ local section="$1"
+
+ config_get custom_domains_list_type "$section" "custom_domains_list_type" "disabled"
+
+ if [ "$custom_domains_list_type" != "disabled" ]; then
+ log "Adding a custom domains list for section $section"
+ if [ "$custom_domains_list_type" = "dynamic" ]; then
+ # Handle list domains from custom_domains
+ config_list_foreach "$section" custom_domains "sing_box_ruleset_domains" "$section"
+ elif [ "$custom_domains_list_type" = "text" ]; then
+ # Handle domains from text
+ config_get custom_domains_text "$section" "custom_domains_text"
+ process_domains_text "$custom_domains_text" "$section"
+ fi
+ fi
+}
+
+sing_box_ruleset_remote() {
+ log "Configure ruleset remote in sing-box"
+
+ local tag=$1
+ local type=$2
+ local update_interval=$3
+
+ url="$SRS_MAIN_URL/$tag.srs"
+
+ jq \
+ --arg tag "$tag" \
+ --arg type "$type" \
+ --arg url "$url" \
+ --arg update_interval "$update_interval" \
+ '.route |= (if . == null then {rule_set: []} else . end) |
+ .route.rule_set += [{
+ "tag": $tag,
+ "type": $type,
+ "format": "binary",
+ "url": $url,
+ "update_interval": $update_interval
+ }]' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
+}
+
+list_subnets_download() {
+ local service="$1"
+ local table="PodkopTable"
+
+ case "$service" in
+ "twitter")
+ URL=$SUBNETS_TWITTER
+ ;;
+ "meta")
+ URL=$SUBNETS_META
+ ;;
+ "telegram")
+ URL=$SUBNETS_TELERAM
+ ;;
+ "discord")
+ URL=$SUBNETS_DISCORD
+ 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
+ ;;
+ *)
+ return
+ ;;
+ esac
+
+ local filename=$(basename "$URL")
+ wget -q -O "/tmp/podkop/$filename" "$URL"
+
+ while IFS= read -r subnet; do
+ if [ "$service" = "discord" ]; then
+ nft add element inet $table podkop_discord_subnets { $subnet }
+ else
+ nft add element inet $table podkop_subnets { $subnet }
+ fi
+ done <"/tmp/podkop/$filename"
+}
+
+sing_box_rules() {
+ log "Configure rule in sing-box"
+ local rule_set="$1"
+ local outbound="$2"
+
+ # Check if there is an outbound rule for “tproxy-in”
+ local rule_exists=$(jq -r '.route.rules[] | select(.outbound == "'"$outbound"'" and .inbound == ["tproxy-in"])' "$SING_BOX_CONFIG")
+
+ if [[ -n "$rule_exists" ]]; then
+ # If a rule for tproxy-in exists, add a new rule_set to the existing rule
+ jq \
+ --arg rule_set "$rule_set" \
+ --arg outbound "$outbound" \
+ '(.route.rules[] | select(.outbound == $outbound and .inbound == ["tproxy-in"]) .rule_set) += [$rule_set]' \
+ "$SING_BOX_CONFIG" >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$SING_BOX_CONFIG"
+ else
+ # If there is no rule for tproxy-in, create a new one with rule_set
+ jq \
+ --arg rule_set "$rule_set" \
+ --arg outbound "$outbound" \
+ '.route.rules += [{
+ "inbound": ["tproxy-in"],
+ "rule_set": [$rule_set],
+ "outbound": $outbound,
+ "action": "route"
+ }]' "$SING_BOX_CONFIG" >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$SING_BOX_CONFIG"
+ fi
+}
+
+process_remote_ruleset() {
+ config_get_bool domain_list_enabled "$section" "domain_list_enabled" "0"
+ if [ "$domain_list_enabled" -eq 1 ]; then
+ log "Adding a srs list for $section"
+ config_list_foreach "$section" domain_list "sing_box_ruleset_remote" "remote" "1d"
+ config_list_foreach "$section" domain_list "list_subnets_download" "$section" "$domain_list"
+ fi
+}
+
+sing_box_rule_preset() {
+ config_get custom_domains_list_type "$section" "custom_domains_list_type"
+ config_get custom_subnets_list_enabled "$section" "custom_subnets_list_enabled"
+
+ if [ "$custom_domains_list_type" != "disabled" ] || [ "$custom_subnets_list_enabled" != "disabled" ]; then
+ sing_box_rules "$section" "$section"
+ sing_box_dns_rule_fakeip_section "$section" "$section"
+ fi
+
+ config_get domain_list_enabled "$section" "domain_list_enabled"
+ config_get domain_list "$section" "domain_list"
+ if [ "$domain_list_enabled" -eq 1 ]; then
+ config_list_foreach $section domain_list sing_box_rules $section
+ config_list_foreach $section domain_list sing_box_dns_rule_fakeip_section domain_list
+ fi
+}
+
+list_custom_local_domains_create() {
+ local section="$2"
+ local local_file="$1"
+ local filename=$(basename "$local_file" | cut -d. -f1)
+
+ while IFS= read -r domain; do
+ log "From local file: $domain"
+ sing_box_ruleset_domains $domain $section
+ done <"$local_file"
+}
+
+process_domains_list_local() {
+ local section="$1"
+
+ config_get custom_local_domains_list_enabled "$section" "custom_local_domains_list_enabled"
+ if [ "$custom_local_domains_list_enabled" -eq 1 ]; then
+ log "Adding a custom domains list from file in $section"
+ config_list_foreach "$section" "custom_local_domains" list_custom_local_domains_create "$section"
+ fi
+}
+
+list_custom_url_domains_create() {
+ local section="$2"
+ local URL="$1"
+ local filename=$(basename "$URL")
+
+ wget -q -O "/tmp/podkop/${filename}" "$URL"
+
+ while IFS= read -r domain; do
+ log "From local file: $domain"
+ sing_box_ruleset_domains $domain $section
+ done <"/tmp/podkop/$filename"
+}
+
+process_domains_list_url() {
+ local section="$1"
+
+ config_get custom_download_domains_list_enabled "$section" "custom_download_domains_list_enabled"
+ if [ "$custom_download_domains_list_enabled" -eq 1 ]; then
+ log "Adding a custom domains list from URL in $section"
+ config_list_foreach "$section" "custom_download_domains" list_custom_url_domains_create "$section"
+ fi
+}
+
+process_subnet_for_section() {
+ local section="$1"
+
+ config_get custom_subnets_list_enabled "$section" "custom_subnets_list_enabled" "disabled"
+ if [ "$custom_subnets_list_enabled" != "disabled" ]; then
+ log "Adding a custom subnet list for section $section"
+ if [ "$custom_subnets_list_enabled" = "dynamic" ]; then
+ # Handle list domains from custom_domains
+ config_list_foreach "$section" custom_subnets "sing_box_ruleset_subnets" "$section"
+ elif [ "$custom_subnets_list_enabled" = "text" ]; then
+ # Handle domains from text
+ config_get custom_subnets_text "$section" "custom_subnets_text"
+ process_subnets_text "$custom_subnets_text" "$section"
+ fi
+ fi
+}
+
+list_custom_url_subnets_create() {
+ local section="$2"
+ local URL="$1"
+ local filename=$(basename "$URL")
+
+ wget -q -O "/tmp/podkop/${filename}" "$URL"
+
+ while IFS= read -r subnet; do
+ log "From local file: $subnet"
+ sing_box_ruleset_subnets $subnet $section
+ done <"/tmp/podkop/$filename"
+}
+
+process_subnet_for_section_remote() {
+ local section="$1"
+
+ config_get custom_download_subnets_list_enabled "$section" "custom_download_subnets_list_enabled" "disabled"
+ if [ "$custom_download_subnets_list_enabled" -eq "1" ]; then
+ log "Adding a custom SUBNET list from URL in $section"
+ config_list_foreach "$section" "custom_download_subnets" list_custom_url_subnets_create "$section"
+ fi
+}
+
+process_all_traffic_for_section() {
+ local section="$1"
+
+ config_get all_traffic_from_ip_enabled "$section" "all_traffic_from_ip_enabled"
+ if [ "$all_traffic_from_ip_enabled" -eq "1" ]; then
+ log "Adding an IP to redirect all traffic"
+ config_list_foreach $section all_traffic_ip list_all_traffic_from_ip
+ config_list_foreach $section all_traffic_ip sing_box_rules_source_ip_cidr "$section" "$all_traffic_ip"
+ fi
+}
+
+sing_box_rules_source_ip_cidr() {
+ log "Configure source_ip_cidr rule in sing-box"
+ local outbound="$2"
+ local source_ip_cidr="$1"
+
+ local current_source_ip_cidr=$(jq -r ".route.rules[] | select(.outbound == \"$outbound\" and .source_ip_cidr) | .rule_set" $SING_BOX_CONFIG)
+
+ if [[ -n "$current_source_ip_cidr" ]]; then
+ jq \
+ --arg source_ip_cidr "$source_ip_cidr" \
+ --arg outbound "$outbound" \
+ '(.route.rules[] | select(.outbound == $outbound) | .source_ip_cidr) += [$source_ip_cidr]' \
+ $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
+ else
+ jq \
+ --arg source_ip_cidr "$source_ip_cidr" \
+ --arg outbound "$outbound" \
+ '.route.rules = [
+ {
+ "inbound": ["tproxy-in"],
+ "source_ip_cidr": [$source_ip_cidr],
+ "outbound": $outbound
+ }
+ ] + .route.rules' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
+ fi
+}
+
+## nftables
+list_all_traffic_from_ip() {
+ local ip="$1"
+ if ! nft list chain inet PodkopTable mangle | grep -q "ip saddr $ip"; then
+ nft add set inet PodkopTable localv4 { type ipv4_addr\; flags interval\; }
+ nft add element inet PodkopTable localv4 { \
+ 0.0.0.0/8, \
+ 10.0.0.0/8, \
+ 127.0.0.0/8, \
+ 169.254.0.0/16, \
+ 172.16.0.0/12, \
+ 192.0.0.0/24, \
+ 192.0.2.0/24, \
+ 192.88.99.0/24, \
+ 192.168.0.0/16, \
+ 198.51.100.0/24, \
+ 203.0.113.0/24, \
+ 224.0.0.0/4, \
+ 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 PodkopTable mangle ip saddr $ip ip daddr @localv4 return
+ fi
+}
+
+# Diagnotics
check_proxy() {
if ! command -v sing-box >/dev/null 2>&1; then
nolog "sing-box is not installed"
@@ -1169,7 +1269,7 @@ check_nft() {
nolog "Checking PodkopTable rules..."
# Список всех возможных сетов
- local sets="podkop_domains podkop_subnets podkop2_domains podkop2_subnets localv4"
+ local sets="podkop_domains podkop_subnets podkop_subnets_discord localv4"
nolog "Sets statistics:"
for set_name in $sets; do
@@ -1243,50 +1343,3 @@ check_all() {
check_three
}
-
-process_domains_text() {
- local text="$1"
- local name="$2"
-
- local tmp_file=$(mktemp)
- echo "$text" > "$tmp_file"
-
- sed 's/[, ]\+/\n/g' "$tmp_file" | while IFS= read -r domain; do
- domain=$(echo "$domain" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
- if [ -n "$domain" ]; then
- list_custom_domains_create "$domain" "$name"
- fi
- done
-
- rm -f "$tmp_file"
-}
-
-process_subnets_text() {
- local text="$1"
- local name="$2"
-
- local tmp_file=$(mktemp)
- echo "$text" > "$tmp_file"
-
- sed 's/[, ]\+/\n/g' "$tmp_file" | while IFS= read -r subnet; do
- subnet=$(echo "$subnet" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
- if [ -n "$subnet" ]; then
- if ! echo "$subnet" | grep -q "/"; then
- subnet="$subnet/32"
- fi
- list_custom_subnets_create "$subnet" "$name"
- fi
- done
-
- rm -f "$tmp_file"
-}
-
-list_custom_subnets_preprocess() {
- local subnet="$1"
- local name="$2"
-
- if ! echo "$subnet" | grep -q "/"; then
- subnet="$subnet/32"
- fi
- list_custom_subnets_create "$subnet" "$name"
-}
\ No newline at end of file