🐤 Sync 2024-07-25 18:50:52

This commit is contained in:
github-actions[bot] 2024-07-25 18:50:52 +08:00
parent 7fa16dced5
commit 4b5a8d5468
34 changed files with 2788 additions and 19 deletions

42
hickory-dns/Makefile Normal file
View File

@ -0,0 +1,42 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=hickory-dns
PKG_VERSION:=master
PKG_RELEASE:=1
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/hickory-dns/hickory-dns.git
PKG_SOURCE_VERSION:=f9da0e946dd1bc62259a2b7dc5c05d7b740fd9a7
PKG_BUILD_DEPENDS:=rust/host
PKG_BUILD_PARALLEL:=1
PKG_BUILD_FLAGS:=no-lto
RUST_PKG_FEATURES:=resolver,dns-over-https-rustls,dns-over-quic,dns-over-h3,native-certs,dnssec-ring,recursor
include $(INCLUDE_DIR)/package.mk
include $(TOPDIR)/feeds/packages/lang/rust/rust-package.mk
define Package/hickory-dns
SECTION:=net
CATEGORY:=Network
SUBMENU:=IP Addresses and Names
TITLE:=A plug-in DNS forwarder/splitter
URL:=https://github.com/hickory-dns/hickory-dns
DEPENDS:=$(RUST_ARCH_DEPENDS)
endef
define Build/Compile
$(call Build/Compile/Cargo,bin,--no-default-features)
endef
define Package/hickory-dns/install
$(INSTALL_DIR) $(1)/usr/bin/
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/bin/* $(1)/usr/bin/
$(INSTALL_DIR) $(1)/etc/init.d/
$(INSTALL_DIR) $(1)/etc/hickory-dns/
$(INSTALL_BIN) ./files/etc/init.d/hickory-dns $(1)/etc/init.d/hickory-dns
$(INSTALL_BIN) ./files/etc/hickory-dns/forwarder.toml $(1)/etc/hickory-dns/forwarder.toml
endef
$(eval $(call RustBinPackage,hickory-dns))
$(eval $(call BuildPackage,hickory-dns))

View File

@ -0,0 +1,12 @@
listen_addrs_ipv6 = ["::0"]
[[zones]]
zone = "."
zone_type = "Forward"
stores = { type = "forward", name_servers = [
{ socket_addr = "[2400:3200::1]:443", protocol = "h3", trust_nx_responses = true, tls_dns_name = "dns.alidns.com" },
{ socket_addr = "[2400:3200:baba::1]:443", protocol = "h3", trust_nx_responses = true, tls_dns_name = "dns.alidns.com" },
{ socket_addr = "1.12.12.12:443", protocol = "https", trust_nx_responses = true, tls_dns_name = "1.12.12.12" },
{ socket_addr = "120.53.53.53:443", protocol = "https", trust_nx_responses = true, tls_dns_name = "120.53.53.53" },
], options = { rotate = true, edns0 = true, ip_strategy = "Ipv6thenIpv4", cache_size = 0, use_hosts_file = true, server_ordering_strategy = "QueryStatistics", shuffle_dns_servers = true }}

View File

@ -0,0 +1,18 @@
#!/bin/sh /etc/rc.common
USE_PROCD=1
START=99
PROG=/usr/bin/hickory-dns
CONF=/etc/hickory-dns/forwarder.toml
start_service() {
procd_open_instance hickory-dns
procd_set_param command $PROG -c $CONF -p 5335
procd_set_param env RUST_LOG=error
procd_set_param user root
procd_set_param stdout 1
procd_set_param stderr 1
procd_set_param respawn "${respawn_threshold:-3600}" "${respawn_timeout:-5}" "${respawn_retry:-5}"
procd_close_instance hickory-dns
}

View File

@ -212,6 +212,7 @@ return view.extend({
o = s.option(widgets.NetworkSelect, 'wan_interfaces', _('WAN Interfaces'));
o.multiple = true;
o.optional = false;
o.retain = true;
o.rmempty = false;
o.depends('transparent_proxy', '1');

View File

@ -0,0 +1,39 @@
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2024-06-19 13:42+0800\n"
"PO-Revision-Date: 2024-06-19 11:44+0800\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: en\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.4.4\n"
"X-Poedit-Basepath: ../../root\n"
"X-Poedit-SearchPath-0: .\n"
#: www/luci-static/resources/view/sblite/main.js:12
msgid "Main Settings"
msgstr ""
#: www/luci-static/resources/view/sblite/main.js:53
msgid "Enable Server"
msgstr ""
#: www/luci-static/resources/view/sblite/main.js:55
msgid "Include Interface"
msgstr ""
#: www/luci-static/resources/view/sblite/main.js:55
msgid "A list of interfaces for which the transparent proxy takes effect"
msgstr ""
#: www/luci-static/resources/view/sblite/main.js:61
msgid "DNS Listen Port"
msgstr ""
#: www/luci-static/resources/view/sblite/main.js:61
msgid "The port number on which the DNS service runs"
msgstr ""

1
luci-app-sblite/po/zh-cn Symbolic link
View File

@ -0,0 +1 @@
zh_Hans

View File

@ -0,0 +1,50 @@
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2024-06-19 13:42+0800\n"
"PO-Revision-Date: 2024-06-19 13:42+0800\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.4.4\n"
"X-Poedit-Basepath: ../../root\n"
"X-Poedit-SearchPath-0: .\n"
#: www/luci-static/resources/view/sblite/main.js:12
msgid "Main Settings"
msgstr "基本设置"
#: www/luci-static/resources/view/sblite/main.js:53
msgid "Enable Server"
msgstr "启用服务"
#: www/luci-static/resources/view/sblite/main.js:55
msgid "Include Interface"
msgstr "包含的接口"
#: www/luci-static/resources/view/sblite/main.js:55
msgid "A list of interfaces for which the transparent proxy takes effect"
msgstr "透明代理生效的内网接口列表"
#: www/luci-static/resources/view/sblite/main.js:61
#, fuzzy
#| msgid "DNS Port"
msgid "DNS Listen Port"
msgstr "DNS 监听端口"
#: www/luci-static/resources/view/sblite/main.js:61
msgid "The port number on which the DNS service runs"
msgstr "DNS 服务运行的端口号"
#~ msgid "TProxy Listen Port"
#~ msgstr "透明代理监听端口"
#~ msgid "The port number on which the proxy service runs"
#~ msgstr "透明代理服务运行的端口号"
#~ msgid "sing-box lite"
#~ msgstr "sing-box lite"

View File

@ -0,0 +1,18 @@
{
"admin/services/sblite": {
"title": "sing-box lite",
"order": 60,
"action": {
"type": "view",
"path": "sblite/main"
},
"depends": {
"acl": [
"luci-app-sblite"
],
"uci": {
"sblite": true
}
}
}
}

View File

@ -0,0 +1,25 @@
{
"luci-app-sblite": {
"description": "Grant access to sblite configuration",
"read": {
"file": {
"/tmp/log/sblite.log": [
"read"
]
},
"ubus": {
"luci.sblite": [
"*"
]
},
"uci": [
"sblite"
]
},
"write": {
"uci": [
"sblite"
]
}
}
}

View File

@ -0,0 +1,14 @@
'use strict';
import * as app from '/usr/share/sblite/export.uc';
const methods = {
subscribe: {
args: { section_id: 'section_id' },
call: function (req) {
return app.subscribe(req.args?.section_id);
}
},
};
return { 'luci.sblite': methods };

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=mihomo
PKG_VERSION:=1.18.6
PKG_RELEASE:=17
PKG_RELEASE:=18
PKG_BUILD_TIME=$(shell date -u -Iseconds)
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz

View File

@ -214,7 +214,7 @@ start_service() {
yq -M -i 'del(.tun)' "$run_profile_path"
# test profile
log "Profile testing..."
if (/usr/bin/mihomo -d "$run_dir" -t > /dev/null 2>&1); then
if (/usr/bin/mihomo -d "$run_dir" -t >> "$run_core_log_path" 2>&1); then
log "Profile test passed!"
else
log "Profile test failed! Exiting..."

View File

@ -5,8 +5,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=naiveproxy
PKG_VERSION:=122.0.6261.43-1
PKG_RELEASE:=100
PKG_VERSION:=125.0.6422.35-1
PKG_RELEASE:=101
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/klzgrad/naiveproxy/tar.gz/v$(PKG_VERSION)?
@ -20,17 +20,6 @@ PKG_BUILD_DEPENDS:=gn/host
PKG_BUILD_PARALLEL:=1
PKG_BUILD_FLAGS:=no-mips16
ifeq ($(strip $(NINJA)),)
ifneq ($(wildcard $(TOPDIR)/feeds/packages/devel/ninja/ninja.mk),)
PKG_BUILD_DEPENDS+=ninja/host
NINJA = \
MAKEFLAGS="$(MAKE_JOBSERVER)" \
$(STAGING_DIR_HOSTPKG)/bin/ninja \
$(if $(findstring c,$(OPENWRT_VERBOSE)),-v) \
$(if $(MAKE_JOBSERVER),,-j1)
endif
endif
ifneq ($(CONFIG_CPU_TYPE)," ")
CPU_TYPE:=$(word 1, $(subst +," ,$(CONFIG_CPU_TYPE)))
CPU_SUBTYPE:=$(word 2, $(subst +, ",$(CONFIG_CPU_TYPE)))
@ -66,7 +55,7 @@ ifneq ($(CONFIG_CCACHE),)
export naive_ccache_flags=cc_wrapper="$(CCACHE)"
endif
CLANG_VER:=18-init-16072-gc4146121e940-5
CLANG_VER:=19-init-8091-gab037c4f-1
CLANG_FILE:=clang-llvmorg-$(CLANG_VER).tgz
define Download/CLANG
URL:=https://commondatastorage.googleapis.com/chromium-browser-clang/Linux_x64
@ -75,7 +64,7 @@ define Download/CLANG
HASH:=skip
endef
PGO_VER:=6261-1707846690-1391fcc4772c0b31e214f533af5cafa87e4ccf40
PGO_VER:=6422-1715102072-9bdbfa29f2bb1ff28f0f031b98501a1193b8d03b-13cfbf145656b369f9c23bff70ab2fb07e1e2fdb
PGO_FILE:=chrome-linux-$(PGO_VER).profdata
define Download/PGO_PROF
URL:=https://storage.googleapis.com/chromium-optimization-profiles/pgo_profiles

View File

@ -55,6 +55,7 @@ use_gio=false
use_gtk=false
use_platform_icu_alternatives=true
use_glib=false
enable_js_protobuf=false
disable_file_support=true
enable_websockets=false

View File

@ -0,0 +1,5 @@
#!/bin/sh
[ "$ACTION" = "ifup" -o "$ACTION" = "ifupdate" ] && [ $(uci get sblite.main.enable) = "1" ] && {
# if "$DEVICE" in outbounds restart sblite
}

View File

@ -0,0 +1,23 @@
#!/bin/sh /etc/rc.common
USE_PROCD=1
START=99
STOP=15
CONFIG=sblite
$APP_FILE=/usr/share/${CONFIG}/app.sh
start_service() {
procd_open_instance $CONFIG
procd_set_param command $APP_FILE start && sing-box run -c /tmp/sblite/config.json
procd_set_param user root
procd_set_param limits core="unlimited"
procd_set_param limits nofile="1000000 1000000"
procd_set_param stdout 1
procd_set_param stderr 1
procd_set_param pidfile /var/run/${CONFIG}.pid
procd_close_instance
}

View File

@ -0,0 +1,17 @@
#!/bin/sh
config_name="sblite"
touch /etc/config/$config_name
section_type="sing_box"
uci set $config_name.main=$section_type
uci set $config_name.access_control=$section_type
uci set $config_name.route=$section_type
uci set $config_name.dns=$section_type
uci set $config_name.subscribe=$section_type
uci commit
/etc/init.d/rpcd reload
exit 0

View File

@ -0,0 +1,26 @@
#!/bin/sh
WORKDIR="$(cd "$(dirname "$0")" && pwd)"
APP_FILE="$WORKDIR/app.uc"
method=$1
shift
case "$method" in
start)
rm -rf /tmp/sblite
mkdir -p /tmp/sblite
mkdir -p /tmp/sblite/rule_sets
ucode -D action=start $APP_FILE
if [ -e '/tmp/sblite/config.json' ]; then
sing-box format -w -c /tmp/sblite/config.json
return 0
fi
return -1
;;
subscribe) ucode -D action=subscribe -D params="$1" $APP_FILE ;;
stop)
rm -rf /tmp/sblite
ucode -D action=stop $APP_FILE
;;
*) ;;
esac

View File

@ -0,0 +1,100 @@
'use strict';
import { cursor } from 'uci';
import { CONF_NAME } from './const.uc';
import { log_t, log_tab } from './utils.uc';
import { start as start_crontab, stop as stop_crontab } from './cron.uc';
import { subscribe } from './subscribe.uc';
import { SingBoxOption } from './singbox.uc';
import * as LOGLEVEL from './loglevel.uc';
import { Outbound } from './outbound.uc';
import { RuleSet } from './rule.uc';
import { Route } from './route.uc';
import { DNS } from './dns.uc';
function start() {
const uci = cursor();
// purne node config
uci.foreach(CONF_NAME, 'node', section => {
const group = section.group;
if (group == null || group == '') {
uci.set(CONF_NAME, section['.name'], 'hashkey', null);
}
});
uci.commit();
const enable = uci.get(CONF_NAME, 'main', 'enable') == '1';
if (enable) {
log_t('starting...');
start_crontab();
const outbounds = Outbound(uci);
const rule_sets = RuleSet(uci, outbounds);
const config = SingBoxOption();
if (uci.get(CONF_NAME, 'main', 'loglevel') != '0') {
let level;
switch (uci.get(CONF_NAME, 'main', 'log')) {
case 'trace': level = LOGLEVEL.TRACE; break;
case 'debug': level = LOGLEVEL.DEBUG; break;
case 'info': level = LOGLEVEL.INFO; break;
case 'warn': level = LOGLEVEL.WARN; break;
case 'error': level = LOGLEVEL.ERROR; break;
case 'fatal': level = LOGLEVEL.FATAL; break;
case 'panic': level = LOGLEVEL.PANIC; break;
default: level = LOGLEVEL.ERROR; break;
}
config.logOption(true, level);
} else {
config.logOption(false);
}
config.logOption(true, LOGLEVEL.ERROR);
config.cacheFileOption(true);
// 出站
config.outbounds = values(outbounds);
const inbounds = {
redirect_tcp: {
type: 'redirect',
tag: 'redirect_tcp',
listen: '::',
listen_port: 18008,
sniff: true,
sniff_override_destination: true,
},
tproxy_udp: {
type: 'tproxy',
tag: 'tproxy_udp',
network: 'udp',
listen: '::',
listen_port: 18008,
sniff: true,
sniff_override_destination: true,
},
};
config.dns = DNS(uci, rule_sets, outbounds);
// 然后是重头戏,路由
config.route = Route(uci, rule_sets, outbounds);
config.inbounds = values(inbounds);
config.write();
log_t('Done.');
} else {
stop_crontab();
}
}
switch (action) {
case 'start':
start();
break;
case 'stop':
const uci = cursor();
uci.set(CONF_NAME, 'main', 'enable', '0');
uci.commit();
start();
break;
case 'subscribe':
subscribe(params);
break;
}

View File

@ -0,0 +1,19 @@
'use strict';
export const APP_FILE = '/usr/share/sblite/app.sh';
export const PKG_NAME = 'sing-box lite';
export const CONF_NAME = 'sblite';
export const LOG_PATH = '/tmp/sblite/singbox.log';
export const DB_PATH = '/tmp/sblite/singbox.db';
export const CONFIG_PATH = '/tmp/sblite/config.json';
export const TCP_IN_TAG = 'tcp-in';
export const UDP_IN_TAG = 'udp-in';
export const DNS_IN_TAG = 'dns-in';
export const FAKE_IP_TAG = 'fake_ip';
export const REJECT_OUTBOUND_TAG = 'reject';
export const DNS_OUTBOUND_TAG = 'dns-out';
export const DNS_BLOCK_TAG = 'block';

View File

@ -0,0 +1,37 @@
'use strict';
import { cursor } from 'uci';
import { APP_FILE, CONF_NAME } from './const.uc';
function clean() {
system('touch /etc/crontabs/root');
system(`sed -i "/sh ${replace(APP_FILE, '/', '\\/')} subscribe cfg.* > \\/dev\\/null 2>&1 &/d" /etc/crontabs/root`);
}
export function start() {
clean();
const uci = cursor();
uci.foreach(CONF_NAME, 'subscription',
function (section) {
if (section.auto_subscribe == '1') {
let day = section.auto_subscribe_daily;
let week = section.auto_subscribe_weekly;
if(week == '0') {
week = '*';
}
const command =`echo "0 ${day} * * ${week} sh ${APP_FILE} subscribe ${section['.name']} > /dev/null 2>&1 &" >> /etc/crontabs/root`;
system(command);
}
}
);
system('/etc/init.d/cron restart');
};
export function stop() {
clean();
system('/etc/init.d/cron restart');
};

View File

@ -0,0 +1,4 @@
'use strict';
export const CLASH_HOST = "127.0.0.1";
export const CLASH_PORT = 9090;

View File

@ -0,0 +1,155 @@
'use strict';
import { log_tab, asip, asport } from './utils.uc';
import { CONF_NAME, DNS_BLOCK_TAG, FAKE_IP_TAG } from './const.uc';
export const PREFER_IPV4 = 'prefer_ipv4';
export const PREFER_IPV6 = 'prefer_ipv6';
export const ONLY_IPV4 = 'ipv4_only';
export const ONLY_IPV6 = 'ipv6_only';
export function DNS(uci, rule_sets, outbounds) {
const result = {
final: '',
strategy: '',
disable_cache: false,
disable_expire: false,
independent_cache: false,
reverse_mapping: false,
};
const servers = {};
uci.foreach(CONF_NAME, 'dns_server', section => {
let strategy = section.strategy;
if (strategy != PREFER_IPV4 && strategy != PREFER_IPV6 && strategy != ONLY_IPV4 && strategy != ONLY_IPV6) {
log_tab('[DNS Server %s] Unkown strategy(%s), fallback to %s', section.tag, strategy, PREFER_IPV4);
strategy = PREFER_IPV4;
}
const server = {
tag: section.tag,
address: section.address,
strategy: strategy,
};
if (section.custom_detour == '1') {
const detour = section.detour;
if (outbounds[detour]) {
server.detour = detour;
} else {
log_tab('[DNS Server %s] Unkown outbound tag(%s)', detour);
}
}
if (section.resolver == '1') {
const tag = section.resolver_tag;
if (outbounds[tag]) {
server.address_resolver = tag;
let strategy = section.resolver_strategy;
if (strategy != PREFER_IPV4 && strategy != PREFER_IPV6 && strategy != ONLY_IPV4 && strategy != ONLY_IPV6) {
log_tab('[DNS Server %s] Unkown resolver strategy(%s), fallback to %s', section.tag, strategy, PREFER_IPV4);
strategy = PREFER_IPV4;
}
server.address_strategy = strategy;
} else {
log_tab('[DNS Server %s] Unkown resolver tag(%s)', tag);
}
}
servers[section.tag] = server;
});
servers[DNS_BLOCK_TAG] = {
address: 'rcode://success',
tag: DNS_BLOCK_TAG,
};
let strategy = uci.get(CONF_NAME, 'dns', 'strategy');
if (strategy != PREFER_IPV4 && strategy != PREFER_IPV6 && strategy != ONLY_IPV4 && strategy != ONLY_IPV6) {
log_tab('[DNS Setting] Unkown strategy(%s), fallback to %s', strategy, PREFER_IPV4);
strategy = PREFER_IPV4;
}
result.strategy = strategy;
if (uci.get(CONF_NAME, 'dns', 'custom_default') == 1) {
const final = uci.get(CONF_NAME, 'dns', 'final');
if (servers[final]) {
result.final = final;
} else {
log_tab('[DNS Setting] Unkown custom defualt dns tag(%s)', final);
}
}
const rules = [];
uci.foreach(CONF_NAME, 'dns_rule', section => {
const server = section.server;
if (servers[server]) {
if (section.rule_set && length(section.rule_set) > 0) {
const current_rule_sets = [];
for (let rule_set in section.rule_set) {
if (rule_sets[rule_set]) {
push(current_rule_sets, rule_set);
} else {
log_tab('[DNS Rule %s] Unkown rule set tag(%s)', rule_set);
}
}
if (length(current_rule_sets) > 0) {
push(rules, {
rule_set: current_rule_sets,
server: server,
});
}
}
} else {
log_tab('[DNS Rule %s] Unkown dns server tag(%s)', server);
}
});
if (uci.get(CONF_NAME, 'dns', 'fake_ip') == '1') {
const fake_ip_inet4_range = uci.get(CONF_NAME, 'dns', 'fake_ip_inet4_range');
const fake_ip_inet6_range = uci.get(CONF_NAME, 'dns', 'fake_ip_inet4_range');
const v4 = asip(fake_ip_inet4_range);
const v6 = asip(fake_ip_inet6_range);
if (v4 && v4.version == 4 && v4.cidr != 32) {
if (v6 && v6.version == 6 && v6.cidr != 128) {
servers[FAKE_IP_TAG] = {
tag: FAKE_IP_TAG,
adress: 'fakeip',
};
push(rules, {
query_type: ['A', 'AAAA'],
server: FAKE_IP_TAG,
});
result.fakeip = {
enabled: true,
inet4_range: `${v4.address}/${v4.cidr}`,
inet6_range: `${v6.address}/${v6.cidr}`,
};
} else {
log_tab('[DNS Setting] Invalid fake ip setting(%s)', fake_ip_inet6_range);
}
} else {
log_tab('[DNS Setting] Invalid fake ip setting(%s)', fake_ip_inet4_range);
}
}
result.servers = values(servers);
result.rules = rules;
return result;
};

View File

@ -0,0 +1,9 @@
'use strict';
import * as CONST from './const.uc';
import * as Subscribe from './subscribe.uc';
import * as Utils from './utils.uc';
export const CONF_NAME = CONST.CONF_NAME;
export const subscribe = Subscribe.subscribe;
export const clear_log = Utils.clear_log;

View File

@ -0,0 +1,9 @@
'use strict';
export const TRACE = 'trace';
export const DEBUG = 'debug';
export const INFO = 'info';
export const WARN = 'warn';
export const ERROR = 'error';
export const FATAL = 'fatal';
export const PANIC = 'panic';

View File

@ -0,0 +1,111 @@
'use strict';
import { log_tab } from './utils.uc';
import { CONF_NAME, REJECT_OUTBOUND_TAG, DNS_OUTBOUND_TAG } from './const.uc';
import { TYPE as vmess_type, outbound as vmess_outbound } from './protocols/vmess.uc';
export function Outbound(uci) {
const result = {};
// 先找 Direct
uci.foreach(CONF_NAME, 'outbound', section => {
if (section.type == 'direct') {
if (result[section.tag]) {
log_tab('Duplicate outbound tag(%s)', section.tag);
} else {
result[section.tag] = {
type: 'direct',
tag: section.tag,
bind_interface: section.interface,
};
}
}
});
const nodes = {};
// 再找 Node
uci.foreach(CONF_NAME, 'node', section => {
if (section.hashkey) {
nodes[section.hashkey] = section;
}
});
uci.foreach(CONF_NAME, 'outbound', section => {
if (section.type == 'node') {
if (result[section.tag]) {
log_tab('Duplicate outbound tag(%s)', section.tag);
} else {
const node = nodes[section.node];
const detour = section.outbound;
if (!result[detour]) {
log_tab('There is no direct outbound(%s)', detour);
return;
}
if (result[detour].type != 'direct') {
log_tab('The outbound(%s) is not direct', detour);
return;
}
if (node) {
switch (node.type) {
case vmess_type:
result[section.tag] = vmess_outbound(node);
break;
default:
log_tab('Unkown protocol(%s) in outbound node(%s)', node.type, section.tag);
return;
}
result[section.tag].tag = section.tag;
result[section.tag].detour = detour;
} else {
log_tab('There is no node with hashkey(%s)', section.node);
}
}
}
});
// 然后找 urltest
uci.foreach(CONF_NAME, 'outbound', section => {
if (section.type == 'urltest') {
if (result[section.tag]) {
log_tab('Duplicate outbound tag(%s)', section.tag);
} else {
result[section.tag] = {
type: 'urltest',
tag: section.tag,
outbounds: [],
url: section.url,
};
for (let tag in section.include) {
const outbound = result[tag];
if (!outbound) {
log_tab('There is no outbound(%s)', tag);
return;
} else if (outbound.type == 'urltest') {
log_tab('The outbound(%s) is urltest, should not be used in another urltest outbound.', tag);
return;
}
push(result[section.tag].outbounds, tag);
}
}
}
});
// 最后添加 BLOCK 出站和 DNS 出站
result[REJECT_OUTBOUND_TAG] = {
type: 'block',
tag: REJECT_OUTBOUND_TAG,
};
result[DNS_OUTBOUND_TAG] = {
type: 'dns',
tag: DNS_OUTBOUND_TAG,
};
return result;
};

View File

@ -0,0 +1,62 @@
'use strict';
import { md5, log_t } from '../utils.uc';
// see: https://github.com/jarryson/singbox-subscribe/blob/main/parsers/vmess.py
export const TYPE = 'vmess';
export function parse(content, result) {
const matches = match(content, /vmess:\/\/(.*)/);
if (matches) {
let payload = matches[1];
const decode = b64dec(payload);
if (decode != null) {
payload = decode;
}
if (payload) {
const info = json(payload);
if (info) {
result.type = TYPE;
result.server = info['add'];
result.server_port = int(info['port']);
result.uuid = info['id'];
result.alter_id = info['aid'] ?? '0';
result.security = info['scy'] ?? 'auto';
if(result.security != 'http') {
result.security = 'auto';
}
result.alias = sprintf('[%s] %s', result.group, info['ps']);
result.hashkey = md5(b64enc(sprintf('[%s] %s://%s:%s?id=%s',
result.group,
result.type,
result.server,
result.server_port,
result.uuid)));
return true;
}
}
}
return false;
};
export function outbound(section) {
return {
type: TYPE,
tag: section.tag,
server: section.server,
server_port: int(section.server_port),
uuid: section.uuid,
security: section.security,
alter_id: int(section.alter_id),
packet_encoding: 'xudp',
};
};

View File

@ -0,0 +1,67 @@
'use strict';
import { CONF_NAME } from './const.uc';
import { log_tab, delete_empty_arr } from './utils.uc';
export function Route(uci, rule_sets, outbounds) {
const result = {
rules: [],
rule_set: [],
};
for (let rule_set in values(rule_sets)) {
push(result.rule_set, rule_set);
}
const route_section = uci.get_all(CONF_NAME, 'route');
if (route_section.custom_default == '1') {
const final_tag = route_section.final;
if (final_tag && rule_sets[final_tag]) {
result.final = final_tag;
}
}
uci.foreach(CONF_NAME, 'route_rule', section => {
const rule = {
invert: section.invert == '1',
};
const outbound = rule.outbound = section.outbound;
if (!outbound || !outbounds[outbound]) {
log_tab('[Route Rule %s] There is no outbound(%s)', section.tag, outbound);
return;
}
if (section.protocol && length(section.protocol) > 0) {
rule.protocol = [];
for (let protocol in section.protocol) {
if (index(['HTTP', 'TLS', 'QUIC', 'STUN', 'BitTorrent'], protocol) > 0) {
push(rule.protocol, protocol);
} else {
log_tab('[Route Rule %s] There is no protocol(%s)', section.tag, protocol);
}
}
delete_empty_arr(rule, 'protocol');
}
if (section.rule_set && length(section.rule_set) > 0) {
rule.rule_set = [];
for (let rule_set in section.rule_set) {
if (rule_sets[rule_set]) {
push(rule.rule_set, rule_set);
} else {
log_tab('[Route Rule %s] There is no rule_set(%s)', section.tag, rule_set);
}
}
delete_empty_arr(rule, 'rule_set');
}
push(result.rules, rule);
});
return result;
};

View File

@ -0,0 +1,278 @@
'use strict';
import { log_tab, asip, asport, delete_empty_arr, call_system_command } from './utils.uc';
import { CONF_NAME } from './const.uc';
import { open as fopen } from 'fs';
function config_ip_and_port(section, section_ip, section_port, config, config_ip, config_port, config_port_range) {
if (section[section_ip]) {
config[config_ip] = [];
for (let line in section[section_ip]) {
const ip = asip(line);
if (ip) {
push(config[config_ip], `${ip.address}/${ip.cidr}`);
} else {
log_tab('Unkown ip (%s) setting in %s', line, section.tag);
}
}
if (length(config[config_ip]) == 0) {
delete config[config_ip];
}
}
if (section[section_port]) {
config[config_port] = [];
config[config_port_range] = [];
for (let line in section[section_port]) {
const port = asport(line);
if (port) {
if (port.range) {
push(config[config_port_range], `${port.start}:${port.end}`);
} else {
push(config[config_port], port.value);
}
} else {
log_tab('Unkown port (%s) setting in %s', line, section.tag);
}
}
delete_empty_arr(config, config_port);
delete_empty_arr(config, config_port_range);
}
}
function rule(section) {
const result = { invert: section.invert == '1' };
switch (section.network) {
case null:
case '0': result.network = ['tcp', 'udp']; break;
case '1': result.network = ['tcp']; break;
case '2': result.network = ['udp']; break;
default:
log_tab('Unkown rule_set network (%s) setting in %s', section.network, section.tag);
result.network = ['tcp', 'udp']; break;
}
config_ip_and_port(section, 'source', 'source_port', result, 'source_ip_cidr', 'source_port', 'source_port_range');
config_ip_and_port(section, 'dest', 'dest_port', result, 'ip_cidr', 'port', 'port_range');
if (section.domain) {
result.domain = [];
result.domain_suffix = [];
result.domain_keyword = [];
result.domain_regex = [];
const lines = split(section.domain, /[(\r\n)\r\n]+/);
for (let line in lines) {
line = trim(line);
let match_result;
if (match_result = match(line, /#.*/)) {
continue;
} else if (match_result = match(line, /domain: *(.+)/)) {
push(result.domain, trim(match_result[1]));
} else if (match_result = match(line, /suffix: *(.+)/)) {
push(result.domain_suffix, trim(match_result[1]));
} else if (match_result = match(line, /keyword: *(.+)/)) {
push(result.domain_keyword, trim(match_result[1]));
} else if (match_result = match(line, /regex: *(.+)/)) {
push(result.domain_regex, trim(match_result[1]));
} else {
log_tab('Unkown rule_set domain (%s) setting in %s', line, section.tag);
}
}
delete_empty_arr(result, 'domain');
delete_empty_arr(result, 'domain_suffix');
delete_empty_arr(result, 'domain_keyword');
delete_empty_arr(result, 'domain_regex');
}
return result;
}
function headless(section, sub_rules) {
if (section.logical_mode && section.logical_mode != '0') {
const headless = {
type: 'logical',
invert: section.invert == '1',
rules: []
};
if (section.logical_mode == '1') {
headless.mode = 'and';
} else if (section.logical_mode == '2') {
headless.mode = 'or';
} else {
log_tab('[Rule Set %s] Unkown rule set logical_mode %s', section.tag, section.logical_mode);
return;
}
if (section.sub_rule) {
for (let sub_rule_tag in section.sub_rule) {
const sub_rule = sub_rules[sub_rule_tag];
if (sub_rule) {
push(headless.rules, sub_rule);
} else {
log_tab('[Rule Set %s] Unkown sub rule set tag %s', section.tag, sub_rule_tag);
}
}
if (length(headless.rules) > 0) {
return headless;
}
}
} else {
const headless = rule(section);
return headless;
}
}
function rule_set(section, headless_rules, sub_rules, outbounds) {
switch (section.type) {
case 'headless':
return;
case 'inline':
const inline = {
type: section.type,
tag: section.tag,
rules: [],
};
if (section.advance != '1') {
const rule = headless(section, sub_rules);
push(inline.rules, rule);
} else {
for (let tag in section.headless) {
const rule = headless_rules[tag];
if (rule) {
push(inline.rules, rule);
} else {
log_tab('[Rule Set %s] Unkown headless rule set tag %s', section.tag, tag);
}
}
}
if (length(inline.rules) > 0) {
const raw = `/tmp/sblite/rule_sets/${section['.name']}.json`;
const srs = `/tmp/sblite/rule_sets/${section['.name']}.srs`;
const fp = fopen(raw, 'w');
if (fp) {
fp.write({
version: 1,
rules: inline.rules,
});
fp.write('\n');
fp.close();
} else {
log_tab('[Rule Set %s] write headless rule to %s failed', section.tag, raw);
return;
}
const compile = call_system_command(`sing-box rule-set compile --output ${srs} ${raw}`);
if(compile && compile != '') {
log_tab('[Rule Set %s] compile headless rule failed\n %s', section.tag, compile);
return;
}
//return inline;
return {
type: 'local',
tag: section.tag,
format: 'binary',
path: srs,
};
}
return;
case 'local':
const local = {
type: section.type,
tag: section.tag,
};
if (section.format == 'binary' || section.format == 'source') {
local.format = section.format;
} else {
log_tab('[Rule Set %s] Unkown rule set format %s', section.tag, section.format);
return;
}
if (section.path) {
local.path = section.path;
} else {
log_tab('[Rule Set %s] Local rule set path undefined', section.tag);
return;
}
return local;
case 'remote':
const remote = {
type: section.type,
tag: section.tag,
};
if (section.format == 'binary' || section.format == 'source') {
remote.format = section.format;
} else {
log_tab('[Rule Set %s] Unkown rule set format %s', section.tag, section.format);
return;
}
if (section.url) {
remote.url = section.url;
} else {
log_tab('[Rule Set %s] Remote rule set url undefined', section.tag);
return;
}
if (section.cutom_detour == '1' && section.download_detour) {
if (outbounds[section.download_detour]) {
remote.download_detour = section.download_detour;
} else {
log_tab('[Rule Set %s] There is no outbound(%s)', section.tag, section.download_detour);
return;
}
}
return remote;
default:
log_tab('[Rule Set %s] Unkown rule set type %s', section.tag, section.type);
return;
}
}
export function RuleSet(uci, outbounds) {
const sub_rules = {}, headless_rules = {}, rule_sets = {};
uci.foreach(CONF_NAME, 'rule_set', section => {
if (section.type == 'headless' && section.sub == '1') {
sub_rules[section.tag] = rule(section);
}
});
uci.foreach(CONF_NAME, 'rule_set', section => {
if (section.type == 'headless' && section.sub != '1') {
headless_rules[section.tag] = headless(section, sub_rules);
}
});
uci.foreach(CONF_NAME, 'rule_set', section => {
if (section.type == 'headless') {
return;
}
const ruleSet = rule_set(section, headless_rules, sub_rules, outbounds);
if (ruleSet) {
rule_sets[section.tag] = ruleSet;
}
});
return rule_sets;
};

View File

@ -0,0 +1,122 @@
'use strict';
import { popen, open as fopen } from 'fs';
import { LOG_PATH, DB_PATH, CONFIG_PATH } from './const.uc';
import { CLASH_PORT } from './default.uc';
import { call_system_command, log_t } from './utils.uc';
function version() {
let result = {
version: 'unkown',
features: {},
};
const handle = popen('/usr/bin/sing-box version');
if (handle) {
for (let line = handle.read('line'); length(line); line = handle.read('line')) {
line = trim(line);
let tags = match(line, /Tags: (.*)/);
if (tags) {
for (let i in split(tags[1], ',')) {
result.features[i] = true;
}
continue;
}
let version = match(line, /sing-box version v(.*)/);
if (version) {
result.version = split(version[1], ' ')[0];
}
}
handle.close();
}
return result;
}
export function SingBoxOption() {
return proto({
log: {},
dns: {},
inbounds: [],
outbounds: [],
route: {},
experimental: {},
}, {
version: version(),
logOption: function (enable, level) {
if (enable) {
this.log.timestamp = true;
this.log.level = level;
this.log.disabled = false;
this.log.output = LOG_PATH;
} else {
this.log.disabled = true;
delete this.log.level;
delete this.log.output;
delete this.log.timestamp;
}
},
cacheFileOption: function (enable) {
if (enable) {
this.experimental.cache_file = {
enabled: true,
path: DB_PATH,
store_fakeip: true,
};
} else {
this.experimental.cache_file = {
enabled: false,
};
}
},
clashOption: function (option) {
// 必须传入 option 的同时 singbox 编译附带了 clash api 选项才能配置
if (!option || !this.version.features.with_clash_api) {
delete this.experimental.clash_api;
return;
}
let port = int(option.port);
if (port == 'NaN' || port < 0 || port > 65535) {
port = CLASH_PORT;
}
let host = option.host;
let ui = option.ui;
let download_url = option.download_url;
let download_detour = option.download_detour;
let secret = option.secret;
let default_mode = option.default_mode;
this.experimental.clash_api = {
external_controller: host + ':' + port,
external_ui: ui,
external_ui_download_url: download_url,
external_ui_download_detour: download_detour,
secret: secret,
default_mode: default_mode,
};
return;
},
write: function () {
const fp = fopen(CONFIG_PATH, 'w');
if (fp) {
fp.write(this);
fp.write('\n');
fp.close();
}
}
});
};

View File

@ -0,0 +1,211 @@
'use strict';
import { cursor } from 'uci';
import { PKG_NAME, CONF_NAME } from './const.uc';
import { wget, log_t, log_tab } from './utils.uc';
import { parse as vmess_parse } from './protocols/vmess.uc';
function filter(name, mode) {
switch (mode.mode) {
case '1': // 按关键字丢弃
return !match(name, mode.excludes);
case '2': // 按关键字保留
return match(name, mode.includes);
case '3': // 按关键字丢弃未匹配成功保留列表的项
return match(name, mode.includes) || !match(name, mode.excludes);
case '4': // 按关键字保留未匹配成功丢弃列表的项
return !match(name, mode.excludes) && match(name, mode.includes);
default:
case '0': // 不过滤
return true;
}
}
function get_filter_mode(mode, includes, excludes) {
const result = {
mode: mode,
};
if (result.mode && result.mode != '0') {
if (result.mode == '1' ||
result.mode == '3' ||
result.mode == '4' &&
excludes &&
length(excludes) > 0) {
excludes = map(excludes, str => replace(str, /[\\.*+?^$|\[(){}]/g, '\\$&'));
result.excludes = regexp(join('|', excludes));
}
if (result.mode == '2' ||
result.mode == '3' ||
result.mode == '4' &&
includes &&
length(includes) > 0) {
includes = map(includes, str => replace(str, /[\\.*+?^$|\[(){}]/g, '\\$&'));
result.includes = regexp(join('|', includes));
}
if (result.mode >= '5') {
result.mode = '0';
}
}
if (result.mode == '3' && !result.includes) {
result.mode = '1';
}
if (result.mode == '4' && !result.excludes) {
result.mode = '2';
}
if (result.mode == '1' && !result.excludes) {
result.mode = '0';
}
if (result.mode == '2' && !result.includes) {
result.mode = '0';
}
return result;
}
function subscribe_one(section, filter_mode, results) {
if (!results) {
results = {};
}
const group = section.tag;
const url = section.subscribe_url;
const no_certificate = section.no_certificate;
const ua = section.ua;
log_t('[%s] start...', group);
log_tab('Filter mode %J', filter_mode);
const handle = wget(url, no_certificate, ua);
if (handle) {
let content = handle.read('all');
const decode = b64dec(content);
if (decode != null) {
content = decode;
}
if (content) {
const lines = split(content, /[\r\n]/g);
for (let line in lines) {
line = trim(line);
if (line != '') {
let result = {
group: group,
};
if (vmess_parse(line, result)) {
//
} else {
continue;
}
if (filter(result.alias, filter_mode)) {
log_tab('Get %s Hash: %s', result.alias, result.hashkey);
results[result.hashkey] = result;
} else {
log_tab('Discard %s', result.alias);
}
}
}
}
log_t('[%s] done.', group);
handle.close();
}
else {
log_t('[%s] error.', group);
}
return results;
}
export function subscribe(section_id) {
const uci = cursor();
let results = {};
const subscribe_section = uci.get_all(CONF_NAME, 'subscribe');
const filter_mode = get_filter_mode(
subscribe_section.filter_mode,
subscribe_section.whitelist,
subscribe_section.blacklist
);
log_tab('Global filter mode %J', filter_mode);
if (section_id) {
const section = uci.get_all(CONF_NAME, section_id);
if (section) {
section_id = section.tag;
if (section.filter_mode == '5') {
results = subscribe_one(section, filter_mode);
} else {
results = subscribe_one(section, get_filter_mode(
section.filter_mode,
section.whitelist,
section.blacklist
));
}
} else {
log_t('can\'t get subscribe config %s...', section_id);
}
} else {
log_t('subscribe starting...');
uci.foreach(CONF_NAME, 'subscription',
function (section) {
if (section.filter_mode == '5') {
results = subscribe_one(section, filter_mode, results);
} else {
results = subscribe_one(section, get_filter_mode(
section.filter_mode,
section.whitelist,
section.blacklist
), results);
}
}
);
log_t('All done.');
}
uci.foreach(CONF_NAME, 'node', section => {
const group = section.group;
if (group != null && group != '') {
if (section_id && group != section_id) {
return;
}
uci.delete(CONF_NAME, section['.name']);
}
});
for (let hashkey in keys(results)) {
const result = results[hashkey];
const sid = uci.add(CONF_NAME, 'node');
for (let option in keys(result)) {
uci.set(CONF_NAME, sid, option, result[option]);
}
}
print(results);
if (uci.commit(CONF_NAME)) {
return results;
}
else {
die('commit failed!');
}
};

View File

@ -0,0 +1,155 @@
'use strict';
import { popen, open as fopen } from 'fs';
const LOG_FILE = '/tmp/log/sblite.log';
export function call_system_command(command) {
const handle = popen(command);
if (handle) {
let content = handle.read('all');
handle.close();
content = trim(content);
if (content != null) {
return content;
}
}
};
// return the content of wget output
export function wget(url, ua, no_certificate) {
let addition_parameters = '';
if (ua) {
addition_parameters += ('--user-agent="' + ua + '" ');
}
if (no_certificate) {
addition_parameters += ('--no-check-certificate ');
}
return popen('wget -q ' + addition_parameters + '-t 3 -T 10 -O- ' + url);
};
// return the md5 value of str
export function md5(str) {
for (let i = 0; i < 3; i++) {
const handle = popen('echo -n "' + str + '" | md5sum');
if (handle) {
let content = handle.read(' ');
handle.close();
content = trim(content);
if (content != null) {
return content;
}
}
}
};
export function parse_port(port) {
port = int(port);
if (port <= 0 || port >= 65535) {
port = 'NaN';
}
return port;
};
export function get_loopback(ipv6) {
let loopback = '127.0.0.1';
if (ipv6) {
loopback = '::1';
}
return loopback;
};
export function delete_empty_arr(obj, arr_name) {
if (length(obj[arr_name]) == 0) {
delete obj[arr_name];
}
};
/**
* 将字符串转化为 ip
* @returns {Object} ip
* @returns {number} ip.version
* @returns {string} ip.address
* @returns {number} ip.cidr
*/
export function asip(str) {
const result = match(str, /^([0-9a-fA-F:.]+)(\/([0-9]+))?$/);
if (result) {
const ip = iptoarr(result[1]);
if (ip) {
let cidr = (result[3] == null ? null : int(result[3]));
if (length(ip) == 4) {
cidr ??= 32;
if (cidr <= 32) {
return { version: 4, address: arrtoip(ip), cidr: cidr };
}
} else if (length(ip) == 16) {
cidr ??= 128;
if (cidr <= 128) {
return { version: 6, address: arrtoip(ip), cidr: cidr };
}
}
}
}
return null;
};
export function asport(str) {
const result = match(str, /^([0-9]+)(-([0-9]+))?$/);
if (result) {
const port0 = int(result[1]);
if (port0 >= 0 && port0 <= 65535) {
const port1 = result[3] == null ? null : int(result[3]);
if (port1) {
if (port1 > port0 && port1 <= 65535) {
return { range: true, start: port0, end: port1 };
}
} else {
return { range: false, value: port0 };
}
}
}
return null;
};
function log(fmt, ...args) {
const fp = fopen(LOG_FILE, 'a');
if (fp) {
fp.write(sprintf(fmt, ...args));
fp.write('\n');
fp.close();
}
};
export function log_t(fmt, ...args) {
let s = sprintf(fmt, ...args);
const now = localtime(time());
log('[%04d-%02d-%02d %02d:%02d:%02d] %s', now.year, now.mon, now.mday, now.hour, now.min, now.sec, s);
};
export function log_tab(fmt, ...args) {
log(' %s', sprintf(fmt, ...args));
};
export function clearlog() {
system('echo "" > ' + LOG_FILE);
};

View File

@ -1,12 +1,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=sms-tool
PKG_RELEASE:=22
PKG_RELEASE:=23
PKG_SOURCE_URL:=https://github.com/obsy/sms_tool
PKG_SOURCE_PROTO:=git
PKG_SOURCE_DATE:=2022-03-21
PKG_SOURCE_VERSION:=1b6ca03284fd65db8799dbf7c6224210093786b0
PKG_SOURCE_VERSION:=fce2b931c8d749c28b8281363950e963c98324eb
PKG_MIRROR_HASH:=skip
include $(INCLUDE_DIR)/package.mk