mirror of
https://github.com/itdoginfo/podkop.git
synced 2025-12-06 11:36:50 +03:00
Compare commits
80 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5eff41a0f | ||
|
|
bb1c06951c | ||
|
|
4999840340 | ||
|
|
6c5a271105 | ||
|
|
e336bb831c | ||
|
|
00db99723c | ||
|
|
5439504de7 | ||
|
|
c3072162de | ||
|
|
d021636f85 | ||
|
|
a06aac0613 | ||
|
|
29159243ea | ||
|
|
269123600a | ||
|
|
49add27f81 | ||
|
|
c929c74da5 | ||
|
|
bb91144a91 | ||
|
|
2291d9fb9d | ||
|
|
f722a513d0 | ||
|
|
a71707f174 | ||
|
|
983f05345b | ||
|
|
ee246895de | ||
|
|
27719f90ee | ||
|
|
4a17cf66a3 | ||
|
|
db956452d1 | ||
|
|
4897d3d292 | ||
|
|
0aa0a4a9c8 | ||
|
|
7d082c5def | ||
|
|
8845749517 | ||
|
|
054ed355cf | ||
|
|
304c57edfa | ||
|
|
8dd33cdde2 | ||
|
|
3d3fbe3bfb | ||
|
|
427ea3bc9a | ||
|
|
a7f6a993ac | ||
|
|
074c1a9349 | ||
|
|
b6a6db71a8 | ||
|
|
38fcb59ed7 | ||
|
|
5a2ffcfd38 | ||
|
|
49f12b212d | ||
|
|
489c61baa2 | ||
|
|
d4b5431db4 | ||
|
|
d0ea39abd0 | ||
|
|
d4e754d2eb | ||
|
|
82f9ae4c6a | ||
|
|
775b0073d3 | ||
|
|
b477a8abc0 | ||
|
|
81e0c86060 | ||
|
|
191522f396 | ||
|
|
79cea7a31a | ||
|
|
6c094aceae | ||
|
|
1e8c2b50f7 | ||
|
|
27d2366208 | ||
|
|
c1133827a2 | ||
|
|
a187192a88 | ||
|
|
fe30cf9e55 | ||
|
|
9496a88774 | ||
|
|
f54e92cd7a | ||
|
|
d70a04b144 | ||
|
|
e5be9c3fd1 | ||
|
|
9762b9cca4 | ||
|
|
9d861cf3e0 | ||
|
|
49836e4adc | ||
|
|
5273935d25 | ||
|
|
d03167f49d | ||
|
|
da89c5c7df | ||
|
|
acfc95e86d | ||
|
|
17c1d09aa8 | ||
|
|
c7e21010bd | ||
|
|
f70e2ac557 | ||
|
|
cb4e3036be | ||
|
|
12fc6bd9ac | ||
|
|
2794cad533 | ||
|
|
9b182a3045 | ||
|
|
f07d90a524 | ||
|
|
75fc377c22 | ||
|
|
33ecb771f9 | ||
|
|
db91c628c8 | ||
|
|
41ce41945c | ||
|
|
2753a44440 | ||
|
|
cd1a4e2a8e | ||
|
|
7e041da8c6 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.idea
|
||||
@@ -1 +0,0 @@
|
||||
disable=SC3036,SC3010,SC3014,SC3015,SC3020,SC3003
|
||||
23
README.md
23
README.md
@@ -4,12 +4,13 @@
|
||||
- При возникновении проблем, нужен технически грамотный фидбэк в чат.
|
||||
- При обновлении **обязательно** [сбрасывайте кэш LuCI](https://podkop.net/docs/clear-browser-cache/).
|
||||
- Также при обновлении всегда заходите в конфигурацию и проверяйте свои настройки. Конфигурация может измениться.
|
||||
- Необходимо минимум 15МБ свободного места на роутере. Роутеры с флешками на 16МБ сразу мимо.
|
||||
- Необходимо минимум 25МБ свободного места на роутере. Роутеры с флешками на 16МБ сразу мимо.
|
||||
- При старте программы редактируется конфиг Dnsmasq.
|
||||
- Podkop редактирует конфиг sing-box. Обязательно сохраните ваш конфиг sing-box перед установкой, если он вам нужен.
|
||||
- Информация здесь может быть устаревшей. Все изменения фиксируются в [телеграм-чате](https://t.me/itdogchat/81758/420321).
|
||||
- [Если у вас не что-то не работает.](https://podkop.net/docs/diagnostics/)
|
||||
- Если у вас установлен Getdomains, [его следует удалить](https://github.com/itdoginfo/domain-routing-openwrt?tab=readme-ov-file#%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82-%D0%B4%D0%BB%D1%8F-%D1%83%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F).
|
||||
- Требуется версия OpenWrt 24.10.
|
||||
|
||||
# Документация
|
||||
https://podkop.net/
|
||||
@@ -28,25 +29,27 @@ sh <(wget -O - https://raw.githubusercontent.com/itdoginfo/podkop/refs/heads/mai
|
||||
Основные задачи в issues.
|
||||
|
||||
## Рефактор
|
||||
- [ ] Очевидные повторения в `/usr/bin/podkop` загнать в переменые
|
||||
- [ ] Возможно поменять структуру
|
||||
- [x] Очевидные повторения в `/usr/bin/podkop` загнать в переменые
|
||||
- [x] Возможно поменять структуру
|
||||
|
||||
## Списки
|
||||
- [ ] CloudFront
|
||||
- [ ] DO
|
||||
- [ ] HODCA
|
||||
- [x] CloudFront
|
||||
- [x] DO
|
||||
- [x] HODCA
|
||||
|
||||
## Будущее
|
||||
- [ ] [Подписка](https://github.com/itdoginfo/podkop/issues/118). Здесь нужна реализация, чтоб для каждой секции помимо ручного выбора, был выбор фильтрации по тегу. Например, для main выбираем ключевые слова NL, DE, FI. А для extra секции фильтруем по RU. И создаётся outbound c urltest в которых перечислены outbound из фильтров.
|
||||
- [ ] Опция, когда все запросы (с роутера в первую очередь), а не только br-lan идут в прокси. С этим связана #95. Требуется много переделать для nftables.
|
||||
- [x] Опция, когда все запросы (с роутера в первую очередь), а не только br-lan идут в прокси. С этим связана #95. Требуется много переделать для nftables.
|
||||
- [ ] Весь трафик в Proxy\VPN. Вопрос, что делать с экстрасекциями в этом случае. FakeIP здесь скорее не нужен, а значит только main секция остаётся. Всё что касается fakeip проверок, придётся выключать в этом режиме.
|
||||
- [x] Поддержка Source format. Нужна расшифровка в json и если присуствуют подсети, заносить их в custom subnet nftset.
|
||||
- [ ] Переделывание функции формирования кастомных списков в JSON. Обрабатывать сразу скопом, а не по одному.
|
||||
- [x] Переделывание функции формирования кастомных списков в JSON. Обрабатывать сразу скопом, а не по одному.
|
||||
- [ ] При успешном запуске переходит в фоновый режим и следит за состоянием sing-box. Если вдруг идёт exit 1, выполняется dnsmasq restore и снова следит за состоянием. Вопрос в том, как это искусcтвенно провернуть. Попробовать положить прокси и посмотреть, останется ли работать DNS в этом случае. И здесь, вероятно, можно обойтись триггером в init.d. [Issue](https://github.com/itdoginfo/podkop/issues/111)
|
||||
- [ ] Формирование конфига sing-box в /tmp
|
||||
- [x] Формирование конфига sing-box в /tmp
|
||||
- [ ] Галочка, которая режет доступ к doh серверам.
|
||||
- [ ] IPv6. Только после наполнения Wiki.
|
||||
|
||||
## Тесты
|
||||
- [ ] Unit тесты (BATS)
|
||||
- [ ] Интеграционые тесты бекенда (OpenWrt rootfs + BATS)
|
||||
- [ ] Интеграционые тесты бекенда (OpenWrt rootfs + BATS)
|
||||
|
||||
[](https://deepwiki.com/itdoginfo/podkop)
|
||||
@@ -20,11 +20,11 @@ ss://MjAyMi1ibGFrZTMtYWVzLTEyOC1nY206Y21lZklCdDhwMTJaZm1QWUplMnNCNThRd3R3NXNKeVp
|
||||
|
||||
## Reality
|
||||
```
|
||||
vless://eb445f4b-ddb4-4c79-86d5-0833fc674379@example.com:443?type=tcp&security=reality&pbk=ARQzddtXPJZHinwkPbgVpah9uwPTuzdjU9GpbUkQJkc&fp=chrome&sni=yahoo.com&sid=6cabf01472a3&spx=%2F&flow=xtls-rprx-vision#vless-reality
|
||||
vless://8100b6eb-3fd1-4e73-8ccf-b4ac961232d6@example.com:443?type=tcp&security=reality&pbk=ARQzddtXPJZHinwkPbgVpah9uwPTuzdjU9GpbUkQJkc&fp=chrome&sni=sni.server.com&sid=6cabf01472a3&spx=%2F&flow=xtls-rprx-vision#vless-reality
|
||||
```
|
||||
|
||||
```
|
||||
vless://UUID@IP:2082?security=reality&sni=dash.cloudflare.com&alpn=h2,http/1.1&allowInsecure=1&fp=chrome&pbk=pukkey&sid=id&type=grpc&encryption=none#vless-reality-strange
|
||||
vless://8100b6eb-3fd1-4e73-8ccf-b4ac961232d6@123.123.123.123:2082?security=reality&sni=sni.server.com&alpn=h2,http/1.1&allowInsecure=1&fp=chrome&pbk=ARQzddtXPJZHinwkPbgVpah9uwPTuzdjU9GpbUkQJkc&sid=6cabf01472a3&type=grpc&encryption=none#vless-reality-strange
|
||||
```
|
||||
|
||||
## TLS
|
||||
@@ -35,34 +35,34 @@ vless://8100b6eb-3fd1-4e73-8ccf-b4ac961232d6@example.com:443?type=tcp&security=t
|
||||
|
||||
2.
|
||||
```
|
||||
vless://8b60389a-7a01-4365-9244-c87f12bb98cf@example.com:443?security=tls&sni=SITE&fp=chrome&type=tcp&flow=xtls-rprx-vision&encryption=none#vless-tls-withot-alpn
|
||||
vless://8100b6eb-3fd1-4e73-8ccf-b4ac961232d6@example.com:443?security=tls&sni=sni.server.com&fp=chrome&type=tcp&flow=xtls-rprx-vision&encryption=none#vless-tls-withot-alpn
|
||||
```
|
||||
3.
|
||||
```
|
||||
vless://8b60389a-7a01-4365-9244-c87f12bb98cf@example.com:443/?type=ws&encryption=none&path=%2Fwebsocket&security=tls&sni=sni.server.com&fp=chrome#vless-tls-ws
|
||||
vless://8100b6eb-3fd1-4e73-8ccf-b4ac961232d6@example.com:443/?type=ws&encryption=none&path=%2Fwebsocket&security=tls&sni=sni.server.com&fp=chrome#vless-tls-ws
|
||||
```
|
||||
|
||||
4.
|
||||
```
|
||||
vless://[someid]@[someserver]?security=tls&sni=[somesni]&type=ws&path=/?ed%3D2560&host=[somesni]&encryption=none#vless-tls-ws-2
|
||||
vless://8100b6eb-3fd1-4e73-8ccf-b4ac961232d6@example.com:443?security=tls&sni=sni.server.com&type=ws&path=/?ed%3D2560&host=sni.server.com&encryption=none#vless-tls-ws-2
|
||||
```
|
||||
|
||||
5.
|
||||
```
|
||||
vless://uuid@server:443?security=tls&sni=server&fp=chrome&type=ws&path=/websocket&encryption=none#vless-tls-ws-3
|
||||
vless://8100b6eb-3fd1-4e73-8ccf-b4ac961232d6@example.com:443?security=tls&sni=sni.server.com&fp=chrome&type=ws&path=/websocket&encryption=none#vless-tls-ws-3
|
||||
```
|
||||
|
||||
6.
|
||||
```
|
||||
vless://33333@example.com:443/?type=ws&encryption=none&path=%2Fwebsocket&security=tls&sni=example.com&fp=chrome#vless-tls-ws-4
|
||||
vless://8100b6eb-3fd1-4e73-8ccf-b4ac961232d6@example.com:443/?type=ws&encryption=none&path=%2Fwebsocket&security=tls&sni=sni.server.com&fp=chrome#vless-tls-ws-4
|
||||
```
|
||||
|
||||
7.
|
||||
```
|
||||
vless://id@sub.domain.example:443?type=ws&path=%2Fdir%2Fpath&host=sub.domain.example&security=tls#configname
|
||||
vless://8100b6eb-3fd1-4e73-8ccf-b4ac961232d6@sub.example.com:443?type=ws&path=%2Fdir%2Fpath&host=sub.example.com&security=tls#configname
|
||||
```
|
||||
|
||||
## No security
|
||||
```
|
||||
vless://8b60389a-7a01-4365-9244-c87f12bb98cf@example.com:443?type=tcp&security=none#vless-tls-no-encrypt
|
||||
vless://8100b6eb-3fd1-4e73-8ccf-b4ac961232d6@example.com:443?type=tcp&security=none#vless-tls-no-encrypt
|
||||
```
|
||||
11
install.sh
11
install.sh
@@ -108,6 +108,15 @@ check_system() {
|
||||
MODEL=$(cat /tmp/sysinfo/model)
|
||||
msg "Router model: $MODEL"
|
||||
|
||||
# Check OpenWrt version
|
||||
openwrt_version=$(cat /etc/openwrt_release | grep DISTRIB_RELEASE | cut -d"'" -f2 | cut -d'.' -f1)
|
||||
if [ "$openwrt_version" = "23" ]; then
|
||||
msg "OpenWrt 23.05 не поддерживается начиная с podkop 0.5.0"
|
||||
msg "Для OpenWrt 23.05 используйте podkop версии 0.4.11 или устанавливайте зависимости и podkop вручную"
|
||||
msg "Подробности: https://podkop.net/docs/install/#%d1%83%d1%81%d1%82%d0%b0%d0%bd%d0%be%d0%b2%d0%ba%d0%b0-%d0%bd%d0%b0-2305"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check available space
|
||||
AVAILABLE_SPACE=$(df /overlay | awk 'NR==2 {print $4}')
|
||||
REQUIRED_SPACE=15360 # 15MB in KB
|
||||
@@ -150,7 +159,7 @@ sing_box() {
|
||||
fi
|
||||
|
||||
sing_box_version=$(sing-box version | head -n 1 | awk '{print $3}')
|
||||
required_version="1.11.1"
|
||||
required_version="1.12.4"
|
||||
|
||||
if [ "$(echo -e "$sing_box_version\n$required_version" | sort -V | head -n 1)" != "$required_version" ]; then
|
||||
msg "sing-box version $sing_box_version is older than required $required_version"
|
||||
|
||||
@@ -50,20 +50,10 @@ function createAdditionalSection(mainSection, network) {
|
||||
return _('DNS server address cannot be empty');
|
||||
}
|
||||
|
||||
const ipRegex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
||||
if (ipRegex.test(value)) {
|
||||
const parts = value.split('.');
|
||||
for (const part of parts) {
|
||||
const num = parseInt(part);
|
||||
if (num < 0 || num > 255) {
|
||||
return _('IP address parts must be between 0 and 255');
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const ipRegex = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}(:[0-9]{1,5})?$/;
|
||||
const domainRegex = /^(?:https:\/\/)?([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\.)+[a-zA-Z]{2,63}(:[0-9]{1,5})?(\/[^?#\s]*)?$/;
|
||||
|
||||
const domainRegex = /^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.[a-zA-Z]{2,}(\/[^\s]*)?$/;
|
||||
if (!domainRegex.test(value)) {
|
||||
if (!ipRegex.test(value) && !domainRegex.test(value)) {
|
||||
return _('Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH');
|
||||
}
|
||||
|
||||
@@ -97,20 +87,10 @@ function createAdditionalSection(mainSection, network) {
|
||||
return _('DNS server address cannot be empty');
|
||||
}
|
||||
|
||||
const ipRegex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
||||
if (ipRegex.test(value)) {
|
||||
const parts = value.split('.');
|
||||
for (const part of parts) {
|
||||
const num = parseInt(part);
|
||||
if (num < 0 || num > 255) {
|
||||
return _('IP address parts must be between 0 and 255');
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const ipRegex = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}(:[0-9]{1,5})?$/;
|
||||
const domainRegex = /^(?:https:\/\/)?([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\.)+[a-zA-Z]{2,63}(:[0-9]{1,5})?(\/[^?#\s]*)?$/;
|
||||
|
||||
const domainRegex = /^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.[a-zA-Z]{2,}(\/[^\s]*)?$/;
|
||||
if (!domainRegex.test(value)) {
|
||||
if (!ipRegex.test(value) && !domainRegex.test(value)) {
|
||||
return _('Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH');
|
||||
}
|
||||
|
||||
@@ -134,10 +114,17 @@ function createAdditionalSection(mainSection, network) {
|
||||
return true;
|
||||
};
|
||||
|
||||
o = mainSection.taboption('additional', form.Value, 'cache_file', _('Cache File Path'), _('Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing'));
|
||||
o.value('/tmp/cache.db', 'RAM (/tmp/cache.db)');
|
||||
o = mainSection.taboption('additional', form.ListValue, 'config_path', _('Config File Path'), _('Select path for sing-box config file. Change this ONLY if you know what you are doing'));
|
||||
o.value('/etc/sing-box/config.json', 'Flash (/etc/sing-box/config.json)');
|
||||
o.value('/tmp/sing-box/config.json', 'RAM (/tmp/sing-box/config.json)');
|
||||
o.default = '/etc/sing-box/config.json';
|
||||
o.rmempty = false;
|
||||
o.ucisection = 'main';
|
||||
|
||||
o = mainSection.taboption('additional', form.Value, 'cache_path', _('Cache File Path'), _('Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing'));
|
||||
o.value('/tmp/sing-box/cache.db', 'RAM (/tmp/sing-box/cache.db)');
|
||||
o.value('/usr/share/sing-box/cache.db', 'Flash (/usr/share/sing-box/cache.db)');
|
||||
o.default = '/tmp/cache.db';
|
||||
o.default = '/tmp/sing-box/cache.db';
|
||||
o.rmempty = false;
|
||||
o.ucisection = 'main';
|
||||
o.validate = function (section_id, value) {
|
||||
@@ -220,6 +207,7 @@ function createAdditionalSection(mainSection, network) {
|
||||
o.rmempty = false;
|
||||
o.ucisection = 'main';
|
||||
|
||||
// TODO(ampetelin): Can be moved to advanced settings in luci
|
||||
// Extra IPs and exclusions (main section)
|
||||
o = mainSection.taboption('basic', form.Flag, 'exclude_from_ip_enabled', _('IP for exclusion'), _('Specify local IP addresses that will never use the configured route'));
|
||||
o.default = '0';
|
||||
|
||||
@@ -32,6 +32,7 @@ function createConfigSection(section, map, network) {
|
||||
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.value('urltest', _('URLTest'));
|
||||
o.default = 'url';
|
||||
o.depends('mode', 'proxy');
|
||||
o.ucisection = s.section;
|
||||
@@ -205,11 +206,16 @@ function createConfigSection(section, map, network) {
|
||||
}
|
||||
};
|
||||
|
||||
o = s.taboption('basic', form.DynamicList, 'urltest_proxy_links', _('URLTest Proxy Links'));
|
||||
o.depends('proxy_config_type', 'urltest');
|
||||
o.placeholder = 'vless:// or ss:// link';
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption('basic', form.Flag, 'ss_uot', _('Shadowsocks UDP over TCP'), _('Apply for SS2022'));
|
||||
o.default = '0';
|
||||
o.depends('mode', 'proxy');
|
||||
o.rmempty = false;
|
||||
o.ucisection = 'main';
|
||||
o.ucisection = s.section;
|
||||
|
||||
o = s.taboption('basic', widgets.DeviceSelect, 'interface', _('Network Interface'), _('Select network interface for VPN connection'));
|
||||
o.depends('mode', 'vpn');
|
||||
@@ -234,18 +240,17 @@ function createConfigSection(section, map, network) {
|
||||
return true;
|
||||
};
|
||||
|
||||
o = s.taboption('basic', form.Flag, 'domain_list_enabled', _('Community Lists'));
|
||||
o = s.taboption('basic', form.Flag, 'community_lists_enabled', _('Community Lists'));
|
||||
o.default = '0';
|
||||
o.rmempty = false;
|
||||
o.ucisection = s.section;
|
||||
|
||||
o = s.taboption('basic', form.DynamicList, 'domain_list', _('Service List'), _('Select predefined service for routing') + ' <a href="https://github.com/itdoginfo/allow-domains" target="_blank">github.com/itdoginfo/allow-domains</a>');
|
||||
o = s.taboption('basic', form.DynamicList, 'community_lists', _('Service List'), _('Select predefined service for routing') + ' <a href="https://github.com/itdoginfo/allow-domains" target="_blank">github.com/itdoginfo/allow-domains</a>');
|
||||
o.placeholder = 'Service list';
|
||||
Object.entries(constants.DOMAIN_LIST_OPTIONS).forEach(([key, label]) => {
|
||||
o.value(key, _(label));
|
||||
});
|
||||
|
||||
o.depends('domain_list_enabled', '1');
|
||||
o.depends('community_lists_enabled', '1');
|
||||
o.rmempty = false;
|
||||
o.ucisection = s.section;
|
||||
|
||||
@@ -302,7 +307,7 @@ function createConfigSection(section, map, network) {
|
||||
}
|
||||
};
|
||||
|
||||
o = s.taboption('basic', form.ListValue, 'custom_domains_list_type', _('User Domain List Type'), _('Select how to add your custom domains'));
|
||||
o = s.taboption('basic', form.ListValue, 'user_domain_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'));
|
||||
@@ -310,9 +315,9 @@ function createConfigSection(section, map, network) {
|
||||
o.rmempty = false;
|
||||
o.ucisection = s.section;
|
||||
|
||||
o = s.taboption('basic', form.DynamicList, 'custom_domains', _('User Domains'), _('Enter domain names without protocols (example: sub.example.com or example.com)'));
|
||||
o = s.taboption('basic', form.DynamicList, 'user_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.depends('user_domain_list_type', 'dynamic');
|
||||
o.rmempty = false;
|
||||
o.ucisection = s.section;
|
||||
o.validate = function (section_id, value) {
|
||||
@@ -324,9 +329,9 @@ function createConfigSection(section, map, network) {
|
||||
return true;
|
||||
};
|
||||
|
||||
o = s.taboption('basic', form.TextValue, 'custom_domains_text', _('User Domains List'), _('Enter domain names separated by comma, space or newline. You can add comments after //'));
|
||||
o = s.taboption('basic', form.TextValue, 'user_domains_text', _('User Domains List'), _('Enter domain names separated by comma, space or newline. You can add comments after //'));
|
||||
o.placeholder = 'example.com, sub.example.com\n// Social networks\ndomain.com test.com // personal domains';
|
||||
o.depends('custom_domains_list_type', 'text');
|
||||
o.depends('user_domain_list_type', 'text');
|
||||
o.rows = 8;
|
||||
o.rmempty = false;
|
||||
o.ucisection = s.section;
|
||||
@@ -365,14 +370,14 @@ function createConfigSection(section, map, network) {
|
||||
return true;
|
||||
};
|
||||
|
||||
o = s.taboption('basic', form.Flag, 'custom_local_domains_list_enabled', _('Local Domain Lists'), _('Use the list from the router filesystem'));
|
||||
o = s.taboption('basic', form.Flag, 'local_domain_lists_enabled', _('Local Domain Lists'), _('Use the list from the router filesystem'));
|
||||
o.default = '0';
|
||||
o.rmempty = false;
|
||||
o.ucisection = s.section;
|
||||
|
||||
o = s.taboption('basic', form.DynamicList, 'custom_local_domains', _('Local Domain Lists Path'), _('Enter the list file path'));
|
||||
o = s.taboption('basic', form.DynamicList, 'local_domain_lists', _('Local Domain List Paths'), _('Enter the list file path'));
|
||||
o.placeholder = '/path/file.lst';
|
||||
o.depends('custom_local_domains_list_enabled', '1');
|
||||
o.depends('local_domain_lists_enabled', '1');
|
||||
o.rmempty = false;
|
||||
o.ucisection = s.section;
|
||||
o.validate = function (section_id, value) {
|
||||
@@ -384,14 +389,14 @@ function createConfigSection(section, map, network) {
|
||||
return true;
|
||||
};
|
||||
|
||||
o = s.taboption('basic', form.Flag, 'custom_download_domains_list_enabled', _('Remote Domain Lists'), _('Download and use domain lists from remote URLs'));
|
||||
o = s.taboption('basic', form.Flag, 'remote_domain_lists_enabled', _('Remote Domain Lists'), _('Download and use domain lists from remote URLs'));
|
||||
o.default = '0';
|
||||
o.rmempty = false;
|
||||
o.ucisection = s.section;
|
||||
|
||||
o = s.taboption('basic', form.DynamicList, 'custom_download_domains', _('Remote Domain URLs'), _('Enter full URLs starting with http:// or https://'));
|
||||
o = s.taboption('basic', form.DynamicList, 'remote_domain_lists', _('Remote Domain URLs'), _('Enter full URLs starting with http:// or https://'));
|
||||
o.placeholder = 'URL';
|
||||
o.depends('custom_download_domains_list_enabled', '1');
|
||||
o.depends('remote_domain_lists_enabled', '1');
|
||||
o.rmempty = false;
|
||||
o.ucisection = s.section;
|
||||
o.validate = function (section_id, value) {
|
||||
@@ -399,7 +404,26 @@ function createConfigSection(section, map, network) {
|
||||
return validateUrl(value);
|
||||
};
|
||||
|
||||
o = s.taboption('basic', form.ListValue, 'custom_subnets_list_enabled', _('User Subnet List Type'), _('Select how to add your custom subnets'));
|
||||
o = s.taboption('basic', form.Flag, 'local_subnet_lists_enabled', _('Local Subnet Lists'), _('Use the list from the router filesystem'));
|
||||
o.default = '0';
|
||||
o.rmempty = false;
|
||||
o.ucisection = s.section;
|
||||
|
||||
o = s.taboption('basic', form.DynamicList, 'local_subnet_lists', _('Local Subnet List Paths'), _('Enter the list file path'));
|
||||
o.placeholder = '/path/file.lst';
|
||||
o.depends('local_subnet_lists_enabled', '1');
|
||||
o.rmempty = false;
|
||||
o.ucisection = s.section;
|
||||
o.validate = function (section_id, value) {
|
||||
if (!value || value.length === 0) return true;
|
||||
const pathRegex = /^\/[a-zA-Z0-9_\-\/\.]+$/;
|
||||
if (!pathRegex.test(value)) {
|
||||
return _('Invalid path format. Path must start with "/" and contain valid characters');
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
o = s.taboption('basic', form.ListValue, 'user_subnet_list_type', _('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)'));
|
||||
@@ -407,9 +431,9 @@ function createConfigSection(section, map, network) {
|
||||
o.rmempty = false;
|
||||
o.ucisection = s.section;
|
||||
|
||||
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 = s.taboption('basic', form.DynamicList, 'user_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.depends('user_subnet_list_type', 'dynamic');
|
||||
o.rmempty = false;
|
||||
o.ucisection = s.section;
|
||||
o.validate = function (section_id, value) {
|
||||
@@ -432,9 +456,9 @@ function createConfigSection(section, map, network) {
|
||||
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. You can add comments after //'));
|
||||
o = s.taboption('basic', form.TextValue, 'user_subnets_text', _('User Subnets List'), _('Enter subnets in CIDR notation or single IP addresses, separated by comma, space or newline. You can add comments after //'));
|
||||
o.placeholder = '103.21.244.0/22\n// Google DNS\n8.8.8.8\n1.1.1.1/32, 9.9.9.9 // Cloudflare and Quad9';
|
||||
o.depends('custom_subnets_list_enabled', 'text');
|
||||
o.depends('user_subnet_list_type', 'text');
|
||||
o.rows = 10;
|
||||
o.rmempty = false;
|
||||
o.ucisection = s.section;
|
||||
@@ -489,14 +513,14 @@ function createConfigSection(section, map, network) {
|
||||
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 = s.taboption('basic', form.Flag, 'remote_subnet_lists_enabled', _('Remote Subnet Lists'), _('Download and use subnet lists from remote URLs'));
|
||||
o.default = '0';
|
||||
o.rmempty = false;
|
||||
o.ucisection = s.section;
|
||||
|
||||
o = s.taboption('basic', form.DynamicList, 'custom_download_subnets', _('Remote Subnet URLs'), _('Enter full URLs starting with http:// or https://'));
|
||||
o = s.taboption('basic', form.DynamicList, 'remote_subnet_lists', _('Remote Subnet URLs'), _('Enter full URLs starting with http:// or https://'));
|
||||
o.placeholder = 'URL';
|
||||
o.depends('custom_download_subnets_list_enabled', '1');
|
||||
o.depends('remote_subnet_lists_enabled', '1');
|
||||
o.rmempty = false;
|
||||
o.ucisection = s.section;
|
||||
o.validate = function (section_id, value) {
|
||||
|
||||
@@ -97,8 +97,8 @@ msgstr "Локальные списки доменов"
|
||||
msgid "Use the list from the router filesystem"
|
||||
msgstr "Использовать список из файловой системы роутера"
|
||||
|
||||
msgid "Local Domain Lists Path"
|
||||
msgstr "Путь к локальным спискам доменов"
|
||||
msgid "Local Domain List Paths"
|
||||
msgstr "Пути к локальным спискам доменов"
|
||||
|
||||
msgid "Enter to the list file path"
|
||||
msgstr "Введите путь к файлу списка"
|
||||
@@ -896,4 +896,13 @@ msgid "Delay in milliseconds before reloading podkop after interface UP"
|
||||
msgstr "Задержка в миллисекундах перед перезагрузкой podkop после поднятия интерфейса"
|
||||
|
||||
msgid "Delay value cannot be empty"
|
||||
msgstr "Значение не может быть пустым"
|
||||
msgstr "Значение не может быть пустым"
|
||||
|
||||
msgid "Local Subnet Lists"
|
||||
msgstr "Локальные списки подсетей"
|
||||
|
||||
msgid "Local Subnet List Paths"
|
||||
msgstr "Пути к локальным спискам подсетей"
|
||||
|
||||
msgid "Config File Path"
|
||||
msgstr "Путь к файлу конфигурации"
|
||||
@@ -97,7 +97,7 @@ msgstr ""
|
||||
msgid "Use the list from the router filesystem"
|
||||
msgstr ""
|
||||
|
||||
msgid "Local Domain Lists Path"
|
||||
msgid "Local Domain List Paths"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enter to the list file path"
|
||||
@@ -1250,4 +1250,13 @@ msgid "Delay in milliseconds before reloading podkop after interface UP"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delay value cannot be empty"
|
||||
msgstr ""
|
||||
|
||||
msgid "Local Subnet Lists"
|
||||
msgstr ""
|
||||
|
||||
msgid "Local Subnet List Paths"
|
||||
msgstr ""
|
||||
|
||||
msgid "Config File Path"
|
||||
msgstr ""
|
||||
@@ -55,6 +55,9 @@ define Package/podkop/install
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) ./files/usr/bin/podkop $(1)/usr/bin/podkop
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/lib/podkop
|
||||
$(CP) ./files/usr/lib/* $(1)/usr/lib/podkop/
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,podkop))
|
||||
|
||||
@@ -4,21 +4,22 @@ config main 'main'
|
||||
option proxy_config_type 'url'
|
||||
#option outbound_json ''
|
||||
option proxy_string ''
|
||||
option domain_list_enabled '1'
|
||||
list domain_list 'russia_inside'
|
||||
option subnets_list_enabled '0'
|
||||
option custom_domains_list_type 'disabled'
|
||||
#list custom_domains ''
|
||||
#option custom_domains_text ''
|
||||
option custom_local_domains_list_enabled '0'
|
||||
#list custom_local_domains ''
|
||||
option custom_download_domains_list_enabled '0'
|
||||
#list custom_download_domains ''
|
||||
option custom_domains_list_type 'disable'
|
||||
#list custom_subnets ''
|
||||
#custom_subnets_text ''
|
||||
option custom_download_subnets_list_enabled '0'
|
||||
#list custom_download_subnets ''
|
||||
option community_lists_enabled '1'
|
||||
list community_lists 'russia_inside'
|
||||
option user_domain_list_type 'disabled'
|
||||
#list user_domains ''
|
||||
#option user_domains_text ''
|
||||
option local_domain_lists_enabled '0'
|
||||
#list local_domain_lists ''
|
||||
option remote_domain_lists_enabled '0'
|
||||
#list remote_domain_lists ''
|
||||
option user_subnet_list_type 'disable'
|
||||
#list user_subnets ''
|
||||
#option user_subnets_text ''
|
||||
option local_subnet_lists_enabled '0'
|
||||
#list local_subnet_lists ''
|
||||
option remote_subnet_lists_enabled '0'
|
||||
#list remote_subnet_lists ''
|
||||
option all_traffic_from_ip_enabled '0'
|
||||
#list all_traffic_ip ''
|
||||
option exclude_from_ip_enabled '0'
|
||||
@@ -35,10 +36,12 @@ config main 'main'
|
||||
option split_dns_type 'udp'
|
||||
option split_dns_server '1.1.1.1'
|
||||
option dns_rewrite_ttl '60'
|
||||
option cache_file '/tmp/cache.db'
|
||||
option config_path '/etc/sing-box/config.json'
|
||||
option cache_path '/tmp/sing-box/cache.db'
|
||||
list iface 'br-lan'
|
||||
option mon_restart_ifaces '0'
|
||||
#list restart_ifaces 'wan'
|
||||
option procd_reload_delay '2000'
|
||||
option ss_uot '0'
|
||||
option detour '0'
|
||||
option detour '0'
|
||||
option shutdown_correctly '1'
|
||||
File diff suppressed because it is too large
Load Diff
67
podkop/files/usr/lib/constants.sh
Normal file
67
podkop/files/usr/lib/constants.sh
Normal file
@@ -0,0 +1,67 @@
|
||||
# shellcheck disable=SC2034
|
||||
|
||||
## Common
|
||||
PODKOP_CONFIG="/etc/config/podkop"
|
||||
RESOLV_CONF="/etc/resolv.conf"
|
||||
DNS_RESOLVERS="1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 9.9.9.9 9.9.9.11 94.140.14.14 94.140.15.15 208.67.220.220 208.67.222.222 77.88.8.1 77.88.8.8"
|
||||
CHECK_PROXY_IP_DOMAIN="ip.podkop.fyi"
|
||||
FAKEIP_TEST_DOMAIN="fakeip.podkop.fyi"
|
||||
TMP_SING_BOX_FOLDER="/tmp/sing-box"
|
||||
TMP_RULESET_FOLDER="$TMP_SING_BOX_FOLDER/rulesets"
|
||||
CLOUDFLARE_OCTETS="8.47 162.159 188.114" # Endpoints https://github.com/ampetelin/warp-endpoint-checker
|
||||
|
||||
## nft
|
||||
NFT_TABLE_NAME="PodkopTable"
|
||||
NFT_LOCALV4_SET_NAME="localv4"
|
||||
NFT_COMMON_SET_NAME="podkop_subnets"
|
||||
NFT_DISCORD_SET_NAME="podkop_discord_subnets"
|
||||
NFT_INTERFACE_SET_NAME="interfaces"
|
||||
|
||||
## sing-box
|
||||
# Log
|
||||
SB_DEFAULT_LOG_LEVEL="warn"
|
||||
# DNS
|
||||
SB_DNS_SERVER_TAG="dns-server"
|
||||
SB_SPLIT_DNS_SERVER_TAG="split-dns-server"
|
||||
SB_FAKEIP_DNS_SERVER_TAG="fakeip-server"
|
||||
SB_FAKEIP_INET4_RANGE="198.18.0.0/15"
|
||||
SB_DNS_DOMAIN_RESOLVER_TAG="dns-domain-resolver"
|
||||
SB_FAKEIP_DNS_RULE_TAG="fakeip-dns-rule-tag"
|
||||
SB_INVERT_FAKEIP_DNS_RULE_TAG="invert-fakeip-dns-rule-tag"
|
||||
# Inbounds
|
||||
SB_TPROXY_INBOUND_TAG="tproxy-in"
|
||||
SB_TPROXY_INBOUND_ADDRESS="127.0.0.1"
|
||||
SB_TPROXY_INBOUND_PORT=1602
|
||||
SB_DNS_INBOUND_TAG="dns-in"
|
||||
SB_DNS_INBOUND_ADDRESS="127.0.0.42"
|
||||
SB_DNS_INBOUND_PORT=53
|
||||
SB_MIXED_INBOUND_TAG="mixed-in"
|
||||
SB_MIXED_INBOUND_ADDRESS="0.0.0.0" # TODO(ampetelin): maybe to determine address?
|
||||
SB_MIXED_INBOUND_PORT=2080
|
||||
SB_SERVICE_MIXED_INBOUND_TAG="service-mixed-in"
|
||||
SB_SERVICE_MIXED_INBOUND_ADDRESS="127.0.0.1"
|
||||
SB_SERVICE_MIXED_INBOUND_PORT=4534
|
||||
# Outbounds
|
||||
SB_DIRECT_OUTBOUND_TAG="direct-out"
|
||||
SB_MAIN_OUTBOUND_TAG="main-out"
|
||||
# Route
|
||||
SB_REJECT_RULE_TAG="reject-rule-tag"
|
||||
|
||||
## Lists
|
||||
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_TELERAM="${GITHUB_RAW_URL}/Subnets/IPv4/telegram.lst"
|
||||
SUBNETS_CLOUDFLARE="${GITHUB_RAW_URL}/Subnets/IPv4/cloudflare.lst"
|
||||
SUBNETS_HETZNER="${GITHUB_RAW_URL}/Subnets/IPv4/hetzner.lst"
|
||||
SUBNETS_OVH="${GITHUB_RAW_URL}/Subnets/IPv4/ovh.lst"
|
||||
SUBNETS_DIGITALOCEAN="${GITHUB_RAW_URL}/Subnets/IPv4/digitalocean.lst"
|
||||
SUBNETS_CLOUDFRONT="${GITHUB_RAW_URL}/Subnets/IPv4/cloudfront.lst"
|
||||
COMMUNITY_DOMAIN_SERVICES="russia_inside russia_outside ukraine_inside geoblock block porn news anime youtube hdrezka tiktok google_ai google_play hodca"
|
||||
COMMUNITY_SUBNET_SERVICES="discord meta twitter cloudflare cloudfront digitalocean hetzner ovh telegram"
|
||||
14
podkop/files/usr/lib/helpers.jq
Normal file
14
podkop/files/usr/lib/helpers.jq
Normal file
@@ -0,0 +1,14 @@
|
||||
def extend_key_value(current_value; new_value):
|
||||
if (current_value | type) == "array" then
|
||||
if (new_value | type) == "array" then
|
||||
current_value + new_value
|
||||
else
|
||||
current_value + [new_value]
|
||||
end
|
||||
else
|
||||
if (new_value | type) == "array" then
|
||||
[current_value] + new_value
|
||||
else
|
||||
[current_value, new_value]
|
||||
end
|
||||
end;
|
||||
357
podkop/files/usr/lib/helpers.sh
Normal file
357
podkop/files/usr/lib/helpers.sh
Normal file
@@ -0,0 +1,357 @@
|
||||
# Check if string is valid IPv4
|
||||
is_ipv4() {
|
||||
local ip="$1"
|
||||
local regex="^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$"
|
||||
[[ "$ip" =~ $regex ]]
|
||||
}
|
||||
|
||||
# Check if string is valid IPv4 with CIDR mask
|
||||
is_ipv4_cidr() {
|
||||
local ip="$1"
|
||||
local regex="^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}(\/(3[0-2]|2[0-9]|1[0-9]|[0-9]))$"
|
||||
[[ "$ip" =~ $regex ]]
|
||||
}
|
||||
|
||||
is_ipv4_ip_or_ipv4_cidr() {
|
||||
is_ipv4 "$1" || is_ipv4_cidr "$1"
|
||||
}
|
||||
|
||||
is_domain() {
|
||||
local str="$1"
|
||||
local regex='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$'
|
||||
|
||||
[[ "$str" =~ $regex ]]
|
||||
}
|
||||
|
||||
is_domain_suffix() {
|
||||
local str="$1"
|
||||
local normalized="${str#.}"
|
||||
|
||||
is_domain "$normalized"
|
||||
}
|
||||
|
||||
# Checks if the given string is a valid base64-encoded sequence
|
||||
is_base64() {
|
||||
local str="$1"
|
||||
|
||||
if echo "$str" | base64 -d > /dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Checks if the given file exists
|
||||
file_exists() {
|
||||
local filepath="$1"
|
||||
|
||||
if [[ -f "$filepath" ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Returns the inbound tag name by appending the postfix to the given section
|
||||
get_inbound_tag_by_section() {
|
||||
local section="$1"
|
||||
local postfix="in"
|
||||
|
||||
echo "$section-$postfix"
|
||||
}
|
||||
|
||||
# Returns the outbound tag name by appending the postfix to the given section
|
||||
get_outbound_tag_by_section() {
|
||||
local section="$1"
|
||||
local postfix="out"
|
||||
|
||||
echo "$section-$postfix"
|
||||
}
|
||||
|
||||
# Constructs and returns a ruleset tag using section, name, optional type, and a fixed postfix
|
||||
get_ruleset_tag() {
|
||||
local section="$1"
|
||||
local name="$2"
|
||||
local type="$3"
|
||||
local postfix="ruleset"
|
||||
|
||||
if [ -n "$type" ]; then
|
||||
echo "$section-$name-$type-$postfix"
|
||||
else
|
||||
echo "$section-$name-$postfix"
|
||||
fi
|
||||
}
|
||||
|
||||
# Determines the ruleset format based on the file extension (json → source, srs → binary)
|
||||
get_ruleset_format_by_file_extension() {
|
||||
local file_extension="$1"
|
||||
|
||||
local format
|
||||
case "$file_extension" in
|
||||
json) format="source" ;;
|
||||
srs) format="binary" ;;
|
||||
*)
|
||||
log "Unsupported file extension: .$file_extension"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "$format"
|
||||
}
|
||||
|
||||
# Converts a comma-separated string into a JSON array string
|
||||
comma_string_to_json_array() {
|
||||
local input="$1"
|
||||
|
||||
if [ -z "$input" ]; then
|
||||
echo "[]"
|
||||
return
|
||||
fi
|
||||
|
||||
local replaced="${input//,/\",\"}"
|
||||
|
||||
echo "[\"$replaced\"]"
|
||||
}
|
||||
|
||||
# Decodes a URL-encoded string
|
||||
url_decode() {
|
||||
local encoded="$1"
|
||||
printf '%b' "$(echo "$encoded" | sed 's/+/ /g; s/%/\\x/g')"
|
||||
}
|
||||
|
||||
# Extracts the userinfo (username[:password]) part from a URL
|
||||
url_get_userinfo() {
|
||||
local url="$1"
|
||||
echo "$url" | sed -n -e 's#^[^:/?]*://##' -e '/@/!d' -e 's/@.*//p'
|
||||
}
|
||||
|
||||
# Extracts the host part from a URL
|
||||
url_get_host() {
|
||||
local url="$1"
|
||||
echo "$url" | sed -n -e 's#^[^:/?]*://##' -e 's#^[^/]*@##' -e 's#\([:/].*\|$\)##p'
|
||||
}
|
||||
|
||||
# Extracts the port number from a URL
|
||||
url_get_port() {
|
||||
local url="$1"
|
||||
echo "$url" | sed -n -e 's#^[^:/?]*://##' -e 's#^[^/]*@##' -e 's#^[^/]*:\([0-9][0-9]*\).*#\1#p'
|
||||
}
|
||||
|
||||
# Extracts the path from a URL (without query or fragment; returns "/" if empty)
|
||||
url_get_path() {
|
||||
local url="$1"
|
||||
echo "$url" | sed -n -e 's#^[^:/?]*://##' -e 's#^[^/]*##' -e 's#\([^?]*\).*#\1#p'
|
||||
}
|
||||
|
||||
# Extracts the value of a specific query parameter from a URL
|
||||
url_get_query_param() {
|
||||
local url="$1"
|
||||
local param="$2"
|
||||
|
||||
local raw
|
||||
raw=$(echo "$url" | sed -n "s/.*[?&]$param=\([^&?#]*\).*/\1/p")
|
||||
|
||||
[ -z "$raw" ] && echo "" && return
|
||||
|
||||
echo "$raw"
|
||||
}
|
||||
|
||||
# Extracts the basename (filename without extension) from a URL
|
||||
url_get_basename() {
|
||||
local url="$1"
|
||||
|
||||
local filename="${url##*/}"
|
||||
local basename="${filename%%.*}"
|
||||
|
||||
echo "$basename"
|
||||
}
|
||||
|
||||
# Extracts and returns the file extension from the given URL
|
||||
url_get_file_extension() {
|
||||
local url="$1"
|
||||
|
||||
local basename="${url##*/}"
|
||||
case "$basename" in
|
||||
*.*) echo "${basename##*.}" ;;
|
||||
*) echo "" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Decodes and returns a base64-encoded string
|
||||
base64_decode() {
|
||||
local str="$1"
|
||||
local decoded_url
|
||||
|
||||
decoded_url="$(echo "$str" | base64 -d 2> /dev/null)"
|
||||
|
||||
echo "$decoded_url"
|
||||
}
|
||||
|
||||
# Generates a unique 16-character ID based on the current timestamp and a random number
|
||||
gen_id() {
|
||||
printf '%s%s' "$(date +%s)" "$RANDOM" | md5sum | cut -c1-16
|
||||
}
|
||||
|
||||
# Adds a missing UCI option with the given value if it does not exist
|
||||
migration_add_new_option() {
|
||||
local package="$1"
|
||||
local section="$2"
|
||||
local option="$3"
|
||||
local value="$4"
|
||||
|
||||
local current
|
||||
current="$(uci -q get "$package.$section.$option")"
|
||||
if [ -z "$current" ]; then
|
||||
log "Adding missing option '$option' with value '$value'"
|
||||
uci set "$package.$section.$option=$value"
|
||||
uci commit "$package"
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Migrates a configuration key in an OpenWrt config file from old_key_name to new_key_name
|
||||
migration_rename_config_key() {
|
||||
local config="$1"
|
||||
local key_type="$2"
|
||||
local old_key_name="$3"
|
||||
local new_key_name="$4"
|
||||
|
||||
if grep -q "$key_type $old_key_name" "$config"; then
|
||||
log "Deprecated $key_type found: $old_key_name migrating to $new_key_name"
|
||||
sed -i "s/$key_type $old_key_name/$key_type $new_key_name/g" "$config"
|
||||
fi
|
||||
}
|
||||
|
||||
# Download URL content directly
|
||||
download_to_stream() {
|
||||
local url="$1"
|
||||
local http_proxy_address="$2"
|
||||
local retries="${3:-3}"
|
||||
local wait="${4:-2}"
|
||||
|
||||
for attempt in $(seq 1 "$retries"); do
|
||||
if [ -n "$http_proxy_address" ]; then
|
||||
http_proxy="http://$http_proxy_address" https_proxy="http://$http_proxy_address" wget -qO- "$url" | sed 's/\r$//' && break
|
||||
else
|
||||
wget -qO- "$url" | sed 's/\r$//' && break
|
||||
fi
|
||||
|
||||
log "Attempt $attempt/$retries to download $url failed" "warn"
|
||||
sleep "$wait"
|
||||
done
|
||||
}
|
||||
|
||||
# Download URL to file
|
||||
download_to_file() {
|
||||
local url="$1"
|
||||
local filepath="$2"
|
||||
local http_proxy_address="$3"
|
||||
local retries="${4:-3}"
|
||||
local wait="${5:-2}"
|
||||
|
||||
for attempt in $(seq 1 "$retries"); do
|
||||
if [ -n "$http_proxy_address" ]; then
|
||||
http_proxy="http://$http_proxy_address" https_proxy="http://$http_proxy_address" wget -O "$filepath" "$url" && break
|
||||
else
|
||||
wget -O "$filepath" "$url" && break
|
||||
fi
|
||||
|
||||
log "Attempt $attempt/$retries to download $url failed" "warn"
|
||||
sleep "$wait"
|
||||
done
|
||||
|
||||
if grep -q $'\r' "$filepath"; then
|
||||
log "Downloaded file has Windows line endings (CRLF). Converting to Unix (LF)"
|
||||
sed -i 's/\r$//' "$filepath"
|
||||
fi
|
||||
}
|
||||
|
||||
# Decompiles a sing-box SRS binary file into a JSON ruleset file
|
||||
decompile_srs_file() {
|
||||
local binary_filepath="$1"
|
||||
local output_filepath="$2"
|
||||
|
||||
log "Decompiling $binary_filepath to $output_filepath" "debug"
|
||||
|
||||
if ! file_exists "$binary_filepath"; then
|
||||
log "File $binary_filepath not found" "error"
|
||||
return 1
|
||||
fi
|
||||
|
||||
sing-box rule-set decompile "$binary_filepath" -o "$output_filepath"
|
||||
if [[ $? -ne 0 ]]; then
|
||||
log "Decompilation command failed for $binary_filepath" "error"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Parses a whitespace-separated string, validates items as either domains
|
||||
# or IPv4 addresses/subnets, and returns a comma-separated string of valid items.
|
||||
# Arguments:
|
||||
# $1 - Input string (space-separated list of items)
|
||||
# $2 - Type of validation ("domains" or "subnets")
|
||||
# Outputs:
|
||||
# Comma-separated string of valid domains or subnets
|
||||
#######################################
|
||||
parse_domain_or_subnet_string_to_commas_string() {
|
||||
local string="$1"
|
||||
local type="$2"
|
||||
|
||||
tmpfile=$(mktemp)
|
||||
printf "%s\n" "$string" | sed 's/\/\/.*//' | tr ', ' '\n' | grep -v '^$' > "$tmpfile"
|
||||
|
||||
result="$(parse_domain_or_subnet_file_to_comma_string "$tmpfile" "$type")"
|
||||
rm -f "$tmpfile"
|
||||
|
||||
echo "$result"
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Parses a file line by line, validates entries as either domains or subnets,
|
||||
# and returns a single comma-separated string of valid items.
|
||||
# Arguments:
|
||||
# $1 - Path to the input file
|
||||
# $2 - Type of validation ("domains" or "subnets")
|
||||
# Outputs:
|
||||
# Comma-separated string of valid domains or subnets
|
||||
#######################################
|
||||
parse_domain_or_subnet_file_to_comma_string() {
|
||||
local filepath="$1"
|
||||
local type="$2"
|
||||
|
||||
local result
|
||||
while IFS= read -r line; do
|
||||
line=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||
|
||||
[ -z "$line" ] && continue
|
||||
|
||||
case "$type" in
|
||||
domains)
|
||||
if ! is_domain_suffix "$line"; then
|
||||
log "'$line' is not a valid domain" "debug"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
subnets)
|
||||
if ! is_ipv4 "$line" && ! is_ipv4_cidr "$line"; then
|
||||
log "'$line' is not IPv4 or IPv4 CIDR" "debug"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
log "Unknown type: $type" "error"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -z "$result" ]; then
|
||||
result="$line"
|
||||
else
|
||||
result="$result,$line"
|
||||
fi
|
||||
done < "$filepath"
|
||||
|
||||
echo "$result"
|
||||
}
|
||||
30
podkop/files/usr/lib/logging.sh
Normal file
30
podkop/files/usr/lib/logging.sh
Normal file
@@ -0,0 +1,30 @@
|
||||
COLOR_CYAN="\033[0;36m"
|
||||
COLOR_GREEN="\033[0;32m"
|
||||
COLOR_RESET="\033[0m"
|
||||
|
||||
log() {
|
||||
local message="$1"
|
||||
local level="$2"
|
||||
|
||||
if [ "$level" == "" ]; then
|
||||
level="info"
|
||||
fi
|
||||
|
||||
logger -t "podkop" "[$level] $message"
|
||||
}
|
||||
|
||||
nolog() {
|
||||
local message="$1"
|
||||
local timestamp
|
||||
timestamp=$(date +"%Y-%m-%d %H:%M:%S")
|
||||
|
||||
echo -e "${COLOR_CYAN}[$timestamp]${COLOR_RESET} ${COLOR_GREEN}$message${COLOR_RESET}"
|
||||
}
|
||||
|
||||
echolog() {
|
||||
local message="$1"
|
||||
local level="$2"
|
||||
|
||||
log "$message" "$level"
|
||||
nolog "$message"
|
||||
}
|
||||
30
podkop/files/usr/lib/nft.sh
Normal file
30
podkop/files/usr/lib/nft.sh
Normal file
@@ -0,0 +1,30 @@
|
||||
# Create an nftables table in the inet family
|
||||
nft_create_table() {
|
||||
local name="$1"
|
||||
|
||||
nft add table inet "$name"
|
||||
}
|
||||
|
||||
# Create a set within a table for storing IPv4 addresses
|
||||
nft_create_ipv4_set() {
|
||||
local table="$1"
|
||||
local name="$2"
|
||||
|
||||
nft add set inet "$table" "$name" '{ type ipv4_addr; flags interval; auto-merge; }'
|
||||
}
|
||||
|
||||
nft_create_ifname_set() {
|
||||
local table="$1"
|
||||
local name="$2"
|
||||
|
||||
nft add set inet "$table" "$name" '{ type ifname; flags interval; }'
|
||||
}
|
||||
|
||||
# Add one or more elements to a set
|
||||
nft_add_set_elements() {
|
||||
local table="$1"
|
||||
local set="$2"
|
||||
local elements="$3"
|
||||
|
||||
nft add element inet "$table" "$set" "{ $elements }"
|
||||
}
|
||||
228
podkop/files/usr/lib/sing_box_config_facade.sh
Normal file
228
podkop/files/usr/lib/sing_box_config_facade.sh
Normal file
@@ -0,0 +1,228 @@
|
||||
PODKOP_LIB="/usr/lib/podkop"
|
||||
. "$PODKOP_LIB/helpers.sh"
|
||||
. "$PODKOP_LIB/sing_box_config_manager.sh"
|
||||
|
||||
sing_box_cf_add_dns_server() {
|
||||
local config="$1"
|
||||
local type="$2"
|
||||
local tag="$3"
|
||||
local server="$4"
|
||||
local domain_resolver="$5"
|
||||
local detour="$6"
|
||||
|
||||
local server_address server_port
|
||||
server_address=$(url_get_host "$server")
|
||||
server_port=$(url_get_port "$server")
|
||||
|
||||
case "$type" in
|
||||
udp)
|
||||
[ -z "$server_port" ] && server_port=53
|
||||
config=$(sing_box_cm_add_udp_dns_server "$config" "$tag" "$server_address" "$server_port" "$domain_resolver" \
|
||||
"$detour")
|
||||
;;
|
||||
dot)
|
||||
[ -z "$server_port" ] && server_port=853
|
||||
config=$(sing_box_cm_add_tls_dns_server "$config" "$tag" "$server_address" "$server_port" "$domain_resolver" \
|
||||
"$detour")
|
||||
;;
|
||||
doh)
|
||||
[ -z "$server_port" ] && server_port=443
|
||||
local path headers
|
||||
path=$(url_get_path "$server")
|
||||
headers="" # TODO(ampetelin): implement it if necessary
|
||||
config=$(sing_box_cm_add_https_dns_server "$config" "$tag" "$server_address" "$server_port" "$path" "$headers" \
|
||||
"$domain_resolver" "$detour")
|
||||
;;
|
||||
*)
|
||||
log "Unsupported DNS server type: $type"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "$config"
|
||||
}
|
||||
|
||||
sing_box_cf_add_mixed_inbound_and_route_rule() {
|
||||
local config="$1"
|
||||
local tag="$2"
|
||||
local listen_address="$3"
|
||||
local listen_port="$4"
|
||||
local outbound="$5"
|
||||
|
||||
config=$(sing_box_cm_add_mixed_inbound "$config" "$tag" "$listen_address" "$listen_port")
|
||||
config=$(sing_box_cm_add_route_rule "$config" "" "$tag" "$outbound")
|
||||
|
||||
echo "$config"
|
||||
}
|
||||
|
||||
sing_box_cf_add_proxy_outbound() {
|
||||
local config="$1"
|
||||
local section="$2"
|
||||
local url="$3"
|
||||
local udp_over_tcp="$4"
|
||||
|
||||
url=$(url_decode "$url")
|
||||
|
||||
local scheme="${url%%://*}"
|
||||
case "$scheme" in
|
||||
vless)
|
||||
local tag host port uuid flow packet_encoding
|
||||
tag=$(get_outbound_tag_by_section "$section")
|
||||
host=$(url_get_host "$url")
|
||||
port=$(url_get_port "$url")
|
||||
uuid=$(url_get_userinfo "$url")
|
||||
flow=$(url_get_query_param "$url" "flow")
|
||||
packet_encoding=$(url_get_query_param "$url" "packetEncoding")
|
||||
|
||||
config=$(sing_box_cm_add_vless_outbound "$config" "$tag" "$host" "$port" "$uuid" "$flow" "" "$packet_encoding")
|
||||
|
||||
local transport
|
||||
transport=$(url_get_query_param "$url" "type")
|
||||
case "$transport" in
|
||||
tcp | raw) ;;
|
||||
ws)
|
||||
local ws_path ws_host ws_early_data
|
||||
ws_path=$(url_get_query_param "$url" "path")
|
||||
ws_host=$(url_get_query_param "$url" "host")
|
||||
ws_early_data=$(url_get_query_param "$url" "ed")
|
||||
|
||||
config=$(sing_box_cm_set_vless_ws_transport "$config" "$tag" "$ws_path" "$ws_host" "$ws_early_data")
|
||||
;;
|
||||
grpc)
|
||||
# TODO(ampetelin): Add handling of optional gRPC parameters; example links are needed.
|
||||
config=$(sing_box_cm_set_vless_grpc_transport "$config" "$tag")
|
||||
;;
|
||||
*)
|
||||
log "Unknown transport '$transport' detected." "error"
|
||||
;;
|
||||
esac
|
||||
|
||||
local security
|
||||
security=$(url_get_query_param "$url" "security")
|
||||
case "$security" in
|
||||
tls | reality)
|
||||
local sni insecure alpn fingerprint public_key short_id
|
||||
sni=$(url_get_query_param "$url" "sni")
|
||||
insecure=$(url_get_query_param "$url" "allowInsecure")
|
||||
alpn=$(comma_string_to_json_array "$(url_get_query_param "$url" "alpn")")
|
||||
fingerprint=$(url_get_query_param "$url" "fp")
|
||||
public_key=$(url_get_query_param "$url" "pbk")
|
||||
short_id=$(url_get_query_param "$url" "sid")
|
||||
|
||||
config=$(
|
||||
sing_box_cm_set_vless_tls \
|
||||
"$config" \
|
||||
"$tag" \
|
||||
"$sni" \
|
||||
"$([ "$insecure" == "1" ] && echo true)" \
|
||||
"$([ "$alpn" == "[]" ] && echo null || echo "$alpn")" \
|
||||
"$fingerprint" \
|
||||
"$public_key" \
|
||||
"$short_id"
|
||||
)
|
||||
;;
|
||||
none) ;;
|
||||
*)
|
||||
log "Unknown security '$security' detected." "error"
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
ss)
|
||||
local userinfo tag host port method password udp_over_tcp
|
||||
|
||||
userinfo=$(url_get_userinfo "$url")
|
||||
if is_base64 "$userinfo"; then
|
||||
userinfo=$(base64_decode "$userinfo")
|
||||
fi
|
||||
|
||||
tag=$(get_outbound_tag_by_section "$section")
|
||||
host=$(url_get_host "$url")
|
||||
port=$(url_get_port "$url")
|
||||
method="${userinfo%%:*}"
|
||||
password="${userinfo#*:}"
|
||||
|
||||
config=$(
|
||||
sing_box_cm_add_shadowsocks_outbound \
|
||||
"$config" \
|
||||
"$tag" \
|
||||
"$host" \
|
||||
"$port" \
|
||||
"$method" \
|
||||
"$password" \
|
||||
"" \
|
||||
"$([ "$udp_over_tcp" == "1" ] && echo 2)" # if udp_over_tcp is enabled, enable version 2
|
||||
)
|
||||
;;
|
||||
*)
|
||||
log "Unsupported proxy $scheme type"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "$config"
|
||||
}
|
||||
|
||||
sing_box_cf_add_json_outbound() {
|
||||
local config="$1"
|
||||
local section="$2"
|
||||
local json_outbound="$3"
|
||||
|
||||
local tag
|
||||
tag=$(get_outbound_tag_by_section "$section")
|
||||
|
||||
config=$(sing_box_cm_add_raw_outbound "$config" "$tag" "$json_outbound")
|
||||
|
||||
echo "$config"
|
||||
}
|
||||
|
||||
sing_box_cf_add_interface_outbound() {
|
||||
local config="$1"
|
||||
local section="$2"
|
||||
local interface_name="$3"
|
||||
|
||||
local tag
|
||||
tag=$(get_outbound_tag_by_section "$section")
|
||||
|
||||
config=$(sing_box_cm_add_interface_outbound "$config" "$tag" "$interface_name")
|
||||
|
||||
echo "$config"
|
||||
}
|
||||
|
||||
sing_box_cf_proxy_domain() {
|
||||
local config="$1"
|
||||
local inbound="$2"
|
||||
local domain="$3"
|
||||
local outbound="$4"
|
||||
|
||||
tag="$(gen_id)"
|
||||
config=$(sing_box_cm_add_route_rule "$config" "$tag" "$inbound" "$outbound")
|
||||
config=$(sing_box_cm_patch_route_rule "$config" "$tag" "domain" "$domain")
|
||||
|
||||
echo "$config"
|
||||
}
|
||||
|
||||
sing_box_cf_override_domain_port() {
|
||||
local config="$1"
|
||||
local domain="$2"
|
||||
local port="$3"
|
||||
|
||||
tag="$(gen_id)"
|
||||
config=$(sing_box_cm_add_options_route_rule "$config" "$tag")
|
||||
config=$(sing_box_cm_patch_route_rule "$config" "$tag" "domain" "$domain")
|
||||
config=$(sing_box_cm_patch_route_rule "$config" "$tag" "override_port" "$port")
|
||||
|
||||
echo "$config"
|
||||
}
|
||||
|
||||
sing_box_cf_add_single_key_reject_rule() {
|
||||
local config="$1"
|
||||
local inbound="$2"
|
||||
local key="$3"
|
||||
local value="$4"
|
||||
|
||||
tag="$(gen_id)"
|
||||
config=$(sing_box_cm_add_reject_route_rule "$config" "$tag" "$inbound")
|
||||
config=$(sing_box_cm_patch_route_rule "$config" "$tag" "$key" "$value")
|
||||
|
||||
echo "$config"
|
||||
}
|
||||
1385
podkop/files/usr/lib/sing_box_config_manager.sh
Normal file
1385
podkop/files/usr/lib/sing_box_config_manager.sh
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user