Update On Mon Apr 28 20:38:09 CEST 2025

This commit is contained in:
github-action[bot]
2025-04-28 20:38:09 +02:00
parent 9698d0e480
commit 21d70bf50e
56 changed files with 1227 additions and 863 deletions

1
.github/update.log vendored
View File

@@ -982,3 +982,4 @@ Update On Fri Apr 25 14:32:58 CEST 2025
Update On Fri Apr 25 20:35:32 CEST 2025
Update On Sat Apr 26 20:33:12 CEST 2025
Update On Sun Apr 27 20:32:16 CEST 2025
Update On Mon Apr 28 20:38:01 CEST 2025

View File

@@ -7,13 +7,13 @@ import (
"time"
"github.com/metacubex/mihomo/common/atomic"
"github.com/metacubex/mihomo/common/batch"
"github.com/metacubex/mihomo/common/singledo"
"github.com/metacubex/mihomo/common/utils"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
"github.com/dlclark/regexp2"
"golang.org/x/sync/errgroup"
)
type HealthCheckOption struct {
@@ -147,7 +147,8 @@ func (hc *HealthCheck) check() {
_, _, _ = hc.singleDo.Do(func() (struct{}, error) {
id := utils.NewUUIDV4().String()
log.Debugln("Start New Health Checking {%s}", id)
b, _ := batch.New[bool](hc.ctx, batch.WithConcurrencyNum[bool](10))
b := new(errgroup.Group)
b.SetLimit(10)
// execute default health check
option := &extraOption{filters: nil, expectedStatus: hc.expectedStatus}
@@ -159,13 +160,13 @@ func (hc *HealthCheck) check() {
hc.execute(b, url, id, option)
}
}
b.Wait()
_ = b.Wait()
log.Debugln("Finish A Health Checking {%s}", id)
return struct{}{}, nil
})
}
func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *extraOption) {
func (hc *HealthCheck) execute(b *errgroup.Group, url, uid string, option *extraOption) {
url = strings.TrimSpace(url)
if len(url) == 0 {
log.Debugln("Health Check has been skipped due to testUrl is empty, {%s}", uid)
@@ -195,13 +196,13 @@ func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *ex
}
p := proxy
b.Go(p.Name(), func() (bool, error) {
b.Go(func() error {
ctx, cancel := context.WithTimeout(hc.ctx, hc.timeout)
defer cancel()
log.Debugln("Health Checking, proxy: %s, url: %s, id: {%s}", p.Name(), url, uid)
_, _ = p.URLTest(ctx, url, expectedStatus)
log.Debugln("Health Checked, proxy: %s, url: %s, alive: %t, delay: %d ms uid: {%s}", p.Name(), url, p.AliveForTestUrl(url), p.LastDelayForTestUrl(url), uid)
return false, nil
return nil
})
}
}

View File

@@ -9,7 +9,6 @@ import (
"time"
"github.com/metacubex/mihomo/common/atomic"
"github.com/metacubex/mihomo/common/batch"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/geodata"
_ "github.com/metacubex/mihomo/component/geodata/standard"
@@ -19,6 +18,7 @@ import (
"github.com/metacubex/mihomo/log"
"github.com/oschwald/maxminddb-golang"
"golang.org/x/sync/errgroup"
)
var (
@@ -169,41 +169,25 @@ func UpdateGeoSite() (err error) {
func updateGeoDatabases() error {
defer runtime.GC()
b, _ := batch.New[interface{}](context.Background())
b := errgroup.Group{}
if geodata.GeoIpEnable() {
if geodata.GeodataMode() {
b.Go("UpdateGeoIp", func() (_ interface{}, err error) {
err = UpdateGeoIp()
return
})
b.Go(UpdateGeoIp)
} else {
b.Go("UpdateMMDB", func() (_ interface{}, err error) {
err = UpdateMMDB()
return
})
b.Go(UpdateMMDB)
}
}
if geodata.ASNEnable() {
b.Go("UpdateASN", func() (_ interface{}, err error) {
err = UpdateASN()
return
})
b.Go(UpdateASN)
}
if geodata.GeoSiteEnable() {
b.Go("UpdateGeoSite", func() (_ interface{}, err error) {
err = UpdateGeoSite()
return
})
b.Go(UpdateGeoSite)
}
if e := b.Wait(); e != nil {
return e.Err
}
return nil
return b.Wait()
}
var ErrGetDatabaseUpdateSkip = errors.New("GEO database is updating, skip")

View File

@@ -375,9 +375,8 @@ func hcCompatibleProvider(proxyProviders map[string]provider.ProxyProvider) {
}
}()
}
}
wg.Wait()
}
func updateSniffer(snifferConfig *sniffer.Config) {

View File

@@ -2,7 +2,7 @@
"manifest_version": 1,
"latest": {
"mihomo": "v1.19.5",
"mihomo_alpha": "alpha-a2f0ee7",
"mihomo_alpha": "alpha-d55b047",
"clash_rs": "v0.7.7",
"clash_premium": "2023-09-05-gdcc8d87",
"clash_rs_alpha": "0.7.7-alpha+sha.0abd79a"
@@ -69,5 +69,5 @@
"linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf"
}
},
"updated_at": "2025-04-25T22:21:31.448Z"
"updated_at": "2025-04-27T22:20:44.396Z"
}

View File

@@ -16,6 +16,7 @@
- Windows 错误的全局快捷键 'Ctrl+Q' 注册
- Vless URL 解码时网络类型错误
- 切换自定义代理地址导致系统代理状态异常
- Macos TUN 默认无效网卡名称
#### 新增了:
- 允许代理主机地址设置为非 127.0.0.1 对 WSL 代理友好

View File

@@ -17,7 +17,7 @@ use tokio::time::Duration;
// Windows only
const SERVICE_URL: &str = "http://127.0.0.1:33211";
const REQUIRED_SERVICE_VERSION: &str = "1.0.5"; // 定义所需的服务版本号
const REQUIRED_SERVICE_VERSION: &str = "1.0.6"; // 定义所需的服务版本号
// 限制重装时间和次数的常量
const REINSTALL_COOLDOWN_SECS: u64 = 300; // 5分钟冷却期

View File

@@ -0,0 +1,85 @@
From e184e8609f8c1cd9fef703f667245b6ebd89c2ed Mon Sep 17 00:00:00 2001
From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
Date: Tue, 3 Oct 2023 14:34:24 +0100
Subject: [PATCH] net: sfp: re-implement ignoring the hardware TX_FAULT signal
Re-implement how we ignore the hardware TX_FAULT signal. Rather than
having a separate boolean for this, use a bitmask of the hardware
signals that we wish to ignore. This gives more flexibility in the
future to ignore other signals such as RX_LOS.
Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
Tested-by: Christian Marangi <ansuelsmth@gmail.com>
Link: https://lore.kernel.org/r/E1qnfXc-008UDY-91@rmk-PC.armlinux.org.uk
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/phy/sfp.c | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
--- a/drivers/net/phy/sfp.c
+++ b/drivers/net/phy/sfp.c
@@ -257,6 +257,7 @@ struct sfp {
unsigned int state_hw_drive;
unsigned int state_hw_mask;
unsigned int state_soft_mask;
+ unsigned int state_ignore_mask;
unsigned int state;
struct delayed_work poll;
@@ -280,7 +281,6 @@ struct sfp {
unsigned int rs_state_mask;
bool have_a2;
- bool tx_fault_ignore;
const struct sfp_quirk *quirk;
@@ -347,7 +347,7 @@ static void sfp_fixup_long_startup(struc
static void sfp_fixup_ignore_tx_fault(struct sfp *sfp)
{
- sfp->tx_fault_ignore = true;
+ sfp->state_ignore_mask |= SFP_F_TX_FAULT;
}
// For 10GBASE-T short-reach modules
@@ -796,7 +796,8 @@ static void sfp_soft_start_poll(struct s
mutex_lock(&sfp->st_mutex);
// Poll the soft state for hardware pins we want to ignore
- sfp->state_soft_mask = ~sfp->state_hw_mask & mask;
+ sfp->state_soft_mask = ~sfp->state_hw_mask & ~sfp->state_ignore_mask &
+ mask;
if (sfp->state_soft_mask & (SFP_F_LOS | SFP_F_TX_FAULT) &&
!sfp->need_poll)
@@ -2321,7 +2322,7 @@ static int sfp_sm_mod_probe(struct sfp *
sfp->module_t_start_up = T_START_UP;
sfp->module_t_wait = T_WAIT;
- sfp->tx_fault_ignore = false;
+ sfp->state_ignore_mask = 0;
if (sfp->id.base.extended_cc == SFF8024_ECC_10GBASE_T_SFI ||
sfp->id.base.extended_cc == SFF8024_ECC_10GBASE_T_SR ||
@@ -2344,6 +2345,8 @@ static int sfp_sm_mod_probe(struct sfp *
if (sfp->quirk && sfp->quirk->fixup)
sfp->quirk->fixup(sfp);
+
+ sfp->state_hw_mask &= ~sfp->state_ignore_mask;
mutex_unlock(&sfp->st_mutex);
return 0;
@@ -2844,10 +2847,7 @@ static void sfp_check_state(struct sfp *
mutex_lock(&sfp->st_mutex);
state = sfp_get_state(sfp);
changed = state ^ sfp->state;
- if (sfp->tx_fault_ignore)
- changed &= SFP_F_PRESENT | SFP_F_LOS;
- else
- changed &= SFP_F_PRESENT | SFP_F_LOS | SFP_F_TX_FAULT;
+ changed &= SFP_F_PRESENT | SFP_F_LOS | SFP_F_TX_FAULT;
for (i = 0; i < GPIO_MAX; i++)
if (changed & BIT(i))

View File

@@ -1,63 +0,0 @@
From 7cc39a6bedbd85f3ff7e16845f310e4ce8d9833f Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 6 Sep 2022 00:31:19 +0100
Subject: [PATCH] net: sfp: add quirk for ATS SFP-GE-T 1000Base-TX module
To: netdev@vger.kernel.org,
linux-kernel@vger.kernel.org,
Russell King <linux@armlinux.org.uk>,
Andrew Lunn <andrew@lunn.ch>,
Heiner Kallweit <hkallweit1@gmail.com>
Cc: David S. Miller <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
Jakub Kicinski <kuba@kernel.org>,
Paolo Abeni <pabeni@redhat.com>,
Josef Schlehofer <pepe.schlehofer@gmail.com>
This copper module comes with broken TX_FAULT indicator which must be
ignored for it to work. Implement ignoring TX_FAULT state bit also
during reset/insertion and mute the warning telling the user that the
module indicates TX_FAULT.
Co-authored-by: Josef Schlehofer <pepe.schlehofer@gmail.com>
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/phy/sfp.c | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
--- a/drivers/net/phy/sfp.c
+++ b/drivers/net/phy/sfp.c
@@ -485,6 +485,9 @@ static const struct sfp_quirk sfp_quirks
SFP_QUIRK("HUAWEI", "MA5671A", sfp_quirk_2500basex,
sfp_fixup_ignore_tx_fault),
+ // OEM SFP-GE-T is 1000Base-T module
+ SFP_QUIRK_F("OEM", "SFP-GE-T", sfp_fixup_ignore_tx_fault),
+
// Lantech 8330-262D-E can operate at 2500base-X, but incorrectly report
// 2500MBd NRZ in their EEPROM
SFP_QUIRK_M("Lantech", "8330-262D-E", sfp_quirk_2500basex),
@@ -2604,7 +2607,8 @@ static void sfp_sm_main(struct sfp *sfp,
* or t_start_up, so assume there is a fault.
*/
sfp_sm_fault(sfp, SFP_S_INIT_TX_FAULT,
- sfp->sm_fault_retries == N_FAULT_INIT);
+ !sfp->tx_fault_ignore &&
+ (sfp->sm_fault_retries == N_FAULT_INIT));
} else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR) {
init_done:
/* Create mdiobus and start trying for PHY */
@@ -2862,10 +2866,12 @@ static void sfp_check_state(struct sfp *
mutex_lock(&sfp->st_mutex);
state = sfp_get_state(sfp);
changed = state ^ sfp->state;
- if (sfp->tx_fault_ignore)
+ if (sfp->tx_fault_ignore) {
changed &= SFP_F_PRESENT | SFP_F_LOS;
- else
+ state &= ~SFP_F_TX_FAULT;
+ } else {
changed &= SFP_F_PRESENT | SFP_F_LOS | SFP_F_TX_FAULT;
+ }
for (i = 0; i < GPIO_MAX; i++)
if (changed & BIT(i))

View File

@@ -7,13 +7,13 @@ import (
"time"
"github.com/metacubex/mihomo/common/atomic"
"github.com/metacubex/mihomo/common/batch"
"github.com/metacubex/mihomo/common/singledo"
"github.com/metacubex/mihomo/common/utils"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
"github.com/dlclark/regexp2"
"golang.org/x/sync/errgroup"
)
type HealthCheckOption struct {
@@ -147,7 +147,8 @@ func (hc *HealthCheck) check() {
_, _, _ = hc.singleDo.Do(func() (struct{}, error) {
id := utils.NewUUIDV4().String()
log.Debugln("Start New Health Checking {%s}", id)
b, _ := batch.New[bool](hc.ctx, batch.WithConcurrencyNum[bool](10))
b := new(errgroup.Group)
b.SetLimit(10)
// execute default health check
option := &extraOption{filters: nil, expectedStatus: hc.expectedStatus}
@@ -159,13 +160,13 @@ func (hc *HealthCheck) check() {
hc.execute(b, url, id, option)
}
}
b.Wait()
_ = b.Wait()
log.Debugln("Finish A Health Checking {%s}", id)
return struct{}{}, nil
})
}
func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *extraOption) {
func (hc *HealthCheck) execute(b *errgroup.Group, url, uid string, option *extraOption) {
url = strings.TrimSpace(url)
if len(url) == 0 {
log.Debugln("Health Check has been skipped due to testUrl is empty, {%s}", uid)
@@ -195,13 +196,13 @@ func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *ex
}
p := proxy
b.Go(p.Name(), func() (bool, error) {
b.Go(func() error {
ctx, cancel := context.WithTimeout(hc.ctx, hc.timeout)
defer cancel()
log.Debugln("Health Checking, proxy: %s, url: %s, id: {%s}", p.Name(), url, uid)
_, _ = p.URLTest(ctx, url, expectedStatus)
log.Debugln("Health Checked, proxy: %s, url: %s, alive: %t, delay: %d ms uid: {%s}", p.Name(), url, p.AliveForTestUrl(url), p.LastDelayForTestUrl(url), uid)
return false, nil
return nil
})
}
}

View File

@@ -9,7 +9,6 @@ import (
"time"
"github.com/metacubex/mihomo/common/atomic"
"github.com/metacubex/mihomo/common/batch"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/geodata"
_ "github.com/metacubex/mihomo/component/geodata/standard"
@@ -19,6 +18,7 @@ import (
"github.com/metacubex/mihomo/log"
"github.com/oschwald/maxminddb-golang"
"golang.org/x/sync/errgroup"
)
var (
@@ -169,41 +169,25 @@ func UpdateGeoSite() (err error) {
func updateGeoDatabases() error {
defer runtime.GC()
b, _ := batch.New[interface{}](context.Background())
b := errgroup.Group{}
if geodata.GeoIpEnable() {
if geodata.GeodataMode() {
b.Go("UpdateGeoIp", func() (_ interface{}, err error) {
err = UpdateGeoIp()
return
})
b.Go(UpdateGeoIp)
} else {
b.Go("UpdateMMDB", func() (_ interface{}, err error) {
err = UpdateMMDB()
return
})
b.Go(UpdateMMDB)
}
}
if geodata.ASNEnable() {
b.Go("UpdateASN", func() (_ interface{}, err error) {
err = UpdateASN()
return
})
b.Go(UpdateASN)
}
if geodata.GeoSiteEnable() {
b.Go("UpdateGeoSite", func() (_ interface{}, err error) {
err = UpdateGeoSite()
return
})
b.Go(UpdateGeoSite)
}
if e := b.Wait(); e != nil {
return e.Err
}
return nil
return b.Wait()
}
var ErrGetDatabaseUpdateSkip = errors.New("GEO database is updating, skip")

View File

@@ -375,9 +375,8 @@ func hcCompatibleProvider(proxyProviders map[string]provider.ProxyProvider) {
}
}()
}
}
wg.Wait()
}
func updateSniffer(snifferConfig *sniffer.Config) {

View File

@@ -9,6 +9,7 @@ local split = api.split
local local_version = api.get_app_version("sing-box")
local version_ge_1_11_0 = api.compare_versions(local_version:match("[^v]+"), ">=", "1.11.0")
local version_ge_1_12_0 = api.compare_versions(local_version:match("[^v]+"), ">=", "1.12.0")
local geosite_all_tag = {}
local geoip_all_tag = {}
@@ -126,6 +127,15 @@ function gen_outbound(flag, node, tag, proxy_table)
detour = node.detour,
}
if version_ge_1_12_0 then
--https://sing-box.sagernet.org/migration/#migrate-outbound-domain-strategy-option-to-domain-resolver
result.domain_strategy = nil
result.domain_resolver = {
server = "direct",
strategy = (node.domain_strategy and node.domain_strategy ~="") and node.domain_strategy or nil
}
end
local tls = nil
if node.tls == "1" then
local alpn = nil
@@ -841,6 +851,7 @@ function gen_config(var)
local direct_dns_tcp_server = var["-direct_dns_tcp_server"]
local direct_dns_dot_server = var["-direct_dns_dot_server"]
local direct_dns_query_strategy = var["-direct_dns_query_strategy"]
local remote_dns_server = var["-remote_dns_server"]
local remote_dns_port = var["-remote_dns_port"]
local remote_dns_udp_server = var["-remote_dns_udp_server"]
local remote_dns_tcp_server = var["-remote_dns_tcp_server"]
@@ -1420,10 +1431,12 @@ function gen_config(var)
fakeip = nil,
}
table.insert(dns.servers, {
tag = "block",
address = "rcode://success",
})
if not version_ge_1_12_0 then --Migrate to new DNS server formats
table.insert(dns.servers, {
tag = "block",
address = "rcode://success",
})
end
if dns_socks_address and dns_socks_port then
default_outTag = "dns_socks_out"
@@ -1444,57 +1457,118 @@ function gen_config(var)
remote_strategy = "ipv6_only"
end
local remote_server = {
tag = "remote",
address_strategy = "prefer_ipv4",
strategy = remote_strategy,
address_resolver = "direct",
detour = default_outTag,
client_subnet = (remote_dns_client_ip and remote_dns_client_ip ~= "") and remote_dns_client_ip or nil,
}
if remote_dns_udp_server then
local server_port = tonumber(remote_dns_port) or 53
remote_server.address = "udp://" .. remote_dns_udp_server .. ":" .. server_port
end
if remote_dns_tcp_server then
remote_server.address = remote_dns_tcp_server
end
if remote_dns_doh_url and remote_dns_doh_host then
remote_server.address = remote_dns_doh_url
end
if remote_server.address then
table.insert(dns.servers, remote_server)
end
local remote_server = {}
local fakedns_tag = "remote_fakeip"
if remote_dns_fake then
dns.fakeip = {
enabled = true,
inet4_range = "198.18.0.0/15",
inet6_range = "fc00::/18",
}
table.insert(dns.servers, {
tag = fakedns_tag,
address = "fakeip",
strategy = remote_strategy,
})
if not experimental then
experimental = {}
end
experimental.cache_file = {
enabled = true,
store_fakeip = true,
path = api.CACHE_PATH .. "/singbox_" .. flag .. ".db"
if not version_ge_1_12_0 then --Migrate to new DNS server formats
remote_server = {
tag = "remote",
address_strategy = "prefer_ipv4",
strategy = remote_strategy,
address_resolver = "direct",
detour = default_outTag,
client_subnet = (remote_dns_client_ip and remote_dns_client_ip ~= "") and remote_dns_client_ip or nil,
}
if remote_dns_udp_server then
local server_port = tonumber(remote_dns_port) or 53
remote_server.address = "udp://" .. remote_dns_udp_server .. ":" .. server_port
end
if remote_dns_tcp_server then
remote_server.address = remote_dns_tcp_server
end
if remote_dns_doh_url and remote_dns_doh_host then
remote_server.address = remote_dns_doh_url
end
if remote_server.address then
table.insert(dns.servers, remote_server)
end
if remote_dns_fake then
dns.fakeip = {
enabled = true,
inet4_range = "198.18.0.0/15",
inet6_range = "fc00::/18",
}
table.insert(dns.servers, {
tag = fakedns_tag,
address = "fakeip",
strategy = remote_strategy,
})
if not experimental then
experimental = {}
end
experimental.cache_file = {
enabled = true,
store_fakeip = true,
path = api.CACHE_PATH .. "/singbox_" .. flag .. ".db"
}
end
else -- Migrate to 1.12 DNS
remote_server = {
tag = "remote",
domain_strategy = remote_strategy,
domain_resolver = "direct",
detour = default_outTag,
}
if remote_dns_udp_server then
local server_port = tonumber(remote_dns_port) or 53
remote_server.type = "udp"
remote_server.server = remote_dns_udp_server
remote_server.server_port = server_port
end
if remote_dns_tcp_server then
local server_port = tonumber(remote_dns_port) or 53
remote_server.type = "tcp"
remote_server.server = remote_dns_server
remote_server.server_port = server_port
end
if remote_dns_doh_url and remote_dns_doh_host then
local server_port = tonumber(remote_dns_port) or 443
remote_server.type = "https"
remote_server.server = remote_dns_doh_host
remote_server.server_port = server_port
end
if remote_server.server then
table.insert(dns.servers, remote_server)
end
if remote_dns_fake then
table.insert(dns.servers, {
tag = fakedns_tag,
type = "fakeip",
inet4_range = "198.18.0.0/15",
inet6_range = "fc00::/18",
})
if not experimental then
experimental = {}
end
experimental.cache_file = {
enabled = true,
store_fakeip = true,
path = api.CACHE_PATH .. "/singbox_" .. flag .. ".db"
}
end
end
local direct_strategy = "prefer_ipv6"
if direct_dns_udp_server or direct_dns_tcp_server or direct_dns_dot_server then
if direct_dns_query_strategy == "UseIPv4" then
direct_strategy = "ipv4_only"
elseif direct_dns_query_strategy == "UseIPv6" then
direct_strategy = "ipv6_only"
end
local domain = {}
local nodes_domain_text = sys.exec('uci show passwall | grep ".address=" | cut -d "\'" -f 2 | grep "[a-zA-Z]$" | sort -u')
string.gsub(nodes_domain_text, '[^' .. "\r\n" .. ']+', function(w)
@@ -1503,40 +1577,61 @@ function gen_config(var)
if #domain > 0 then
table.insert(dns_domain_rules, 1, {
outboundTag = "direct",
domain = domain
domain = domain,
strategy = version_ge_1_12_0 and direct_strategy or nil
})
end
local direct_strategy = "prefer_ipv6"
if direct_dns_query_strategy == "UseIPv4" then
direct_strategy = "ipv4_only"
elseif direct_dns_query_strategy == "UseIPv6" then
direct_strategy = "ipv6_only"
if not version_ge_1_12_0 then --Migrate to new DNS server formats
local direct_dns_server, port
if direct_dns_udp_server then
port = tonumber(direct_dns_port) or 53
direct_dns_server = "udp://" .. direct_dns_udp_server .. ":" .. port
elseif direct_dns_tcp_server then
port = tonumber(direct_dns_port) or 53
direct_dns_server = "tcp://" .. direct_dns_tcp_server .. ":" .. port
elseif direct_dns_dot_server then
port = tonumber(direct_dns_port) or 853
if direct_dns_dot_server:find(":") == nil then
direct_dns_server = "tls://" .. direct_dns_dot_server .. ":" .. port
else
direct_dns_server = "tls://[" .. direct_dns_dot_server .. "]:" .. port
end
end
table.insert(dns.servers, {
tag = "direct",
address = direct_dns_server,
address_strategy = "prefer_ipv6",
strategy = direct_strategy,
detour = "direct",
})
else -- Migrate to 1.12 DNS
local direct_dns_server, port, type
if direct_dns_udp_server then
port = tonumber(direct_dns_port) or 53
direct_dns_server = direct_dns_udp_server
type = "udp"
elseif direct_dns_tcp_server then
port = tonumber(direct_dns_port) or 53
direct_dns_server = direct_dns_tcp_server
type = "tcp"
elseif direct_dns_dot_server then
port = tonumber(direct_dns_port) or 853
direct_dns_server = direct_dns_dot_server
type = "tls"
end
table.insert(dns.servers, {
tag = "direct",
type = type,
server = direct_dns_server,
server_port = port,
domain_strategy = direct_strategy,
detour = "direct",
})
end
local direct_dns_server, port
if direct_dns_udp_server then
port = tonumber(direct_dns_port) or 53
direct_dns_server = "udp://" .. direct_dns_udp_server .. ":" .. port
elseif direct_dns_tcp_server then
port = tonumber(direct_dns_port) or 53
direct_dns_server = "tcp://" .. direct_dns_tcp_server .. ":" .. port
elseif direct_dns_dot_server then
port = tonumber(direct_dns_port) or 853
if direct_dns_dot_server:find(":") == nil then
direct_dns_server = "tls://" .. direct_dns_dot_server .. ":" .. port
else
direct_dns_server = "tls://[" .. direct_dns_dot_server .. "]:" .. port
end
end
table.insert(dns.servers, {
tag = "direct",
address = direct_dns_server,
address_strategy = "prefer_ipv6",
strategy = direct_strategy,
detour = "direct",
})
end
local default_dns_flag = "remote"
@@ -1561,6 +1656,9 @@ function gen_config(var)
end
end
dns.final = default_dns_flag
if version_ge_1_12_0 then -- Migrate to 1.12 DNS
dns.strategy = (default_dns_flag == "direct") and direct_strategy or remote_strategy
end
--按分流顺序DNS
if dns_domain_rules and #dns_domain_rules > 0 then
@@ -1574,15 +1672,24 @@ function gen_config(var)
domain_regex = (value.domain_regex and #value.domain_regex > 0) and value.domain_regex or nil,
rule_set = (value.rule_set and #value.rule_set > 0) and value.rule_set or nil, --适配srs
disable_cache = false,
strategy = (version_ge_1_12_0 and value.outboundTag == "direct") and direct_strategy or nil --Migrate to 1.12 DNS
}
if version_ge_1_12_0 and value.outboundTag == "block" then --Migrate to 1.12 DNS
dns_rule.action = "predefined"
dns_rule.rcode = "NOERROR"
dns_rule.server = nil
dns_rule.disable_cache = nil
end
if value.outboundTag ~= "block" and value.outboundTag ~= "direct" then
dns_rule.server = "remote"
if value.outboundTag ~= COMMON.default_outbound_tag and remote_server.address then
local remote_dns_server = api.clone(remote_server)
remote_dns_server.tag = value.outboundTag
remote_dns_server.detour = value.outboundTag
table.insert(dns.servers, remote_dns_server)
dns_rule.server = remote_dns_server.tag
dns_rule.strategy = version_ge_1_12_0 and remote_strategy or nil --Migrate to 1.12 DNS
dns_rule.client_subnet = (version_ge_1_12_0 and remote_dns_client_ip and remote_dns_client_ip ~= "") and remote_dns_client_ip or nil --Migrate to 1.12 DNS
if value.outboundTag ~= COMMON.default_outbound_tag and (remote_server.address or remote_server.server) then
local remote_shunt_server = api.clone(remote_server)
remote_shunt_server.tag = value.outboundTag
remote_shunt_server.detour = value.outboundTag
table.insert(dns.servers, remote_shunt_server)
dns_rule.server = remote_shunt_server.tag
end
if remote_dns_fake then
local fakedns_dns_rule = api.clone(dns_rule)
@@ -1638,12 +1745,44 @@ function gen_config(var)
--实验性
experimental = experimental,
}
table.insert(outbounds, {
local direct_outbound = {
type = "direct",
tag = "direct",
routing_mark = 255,
domain_strategy = "prefer_ipv6",
})
}
if not version_ge_1_12_0 then --Migrate to 1.12 DNS
direct_outbound.domain_strategy = "prefer_ipv6"
else
local domain_resolver = {
server = "direct",
strategy = "prefer_ipv6"
}
direct_outbound.domain_resolver = domain_resolver
-- 当没有 direct dns 服务器时添加 local
local hasDirect = false
if config.dns and config.dns.servers then
for _, server in ipairs(config.dns.servers) do
if server.tag == "direct" then
hasDirect = true
break
end
end
end
if not hasDirect then
config.dns = {
servers = {
{
type = "local",
tag = "direct"
}
},
}
end
end
table.insert(outbounds,direct_outbound)
table.insert(outbounds, {
type = "block",
tag = "block"

View File

@@ -4349,9 +4349,9 @@ dependencies = [
[[package]]
name = "webpki-roots"
version = "0.26.8"
version = "0.26.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9"
checksum = "29aad86cec885cafd03e8305fd727c418e970a521322c91688414d5b8efba16b"
dependencies = [
"rustls-pki-types",
]

View File

@@ -99,8 +99,8 @@ allow = [
"BSD-2-Clause",
"BSD-3-Clause",
"Unicode-3.0",
"Unicode-DFS-2016",
"MPL-2.0",
"CDLA-Permissive-2.0",
"CC0-1.0",
"0BSD",
]
@@ -116,7 +116,6 @@ exceptions = [
# list
#{ allow = ["Zlib"], crate = "adler32" },
{ allow = ["WTFPL"], crate = "tun" },
{ allow = ["LicenseRef-ring"], crate = "ring" },
]
# Some crates don't have (easily) machine readable licensing information,

View File

@@ -1,158 +0,0 @@
package humanize
import (
"fmt"
"math"
"strconv"
"strings"
"unicode"
)
// IEC Sizes.
// kibis of bits
const (
Byte = 1 << (iota * 10)
KiByte
MiByte
GiByte
TiByte
PiByte
EiByte
)
// SI Sizes.
const (
IByte = 1
KByte = IByte * 1000
MByte = KByte * 1000
GByte = MByte * 1000
TByte = GByte * 1000
PByte = TByte * 1000
EByte = PByte * 1000
)
var defaultSizeTable = map[string]uint64{
"b": Byte,
"kib": KiByte,
"kb": KByte,
"mib": MiByte,
"mb": MByte,
"gib": GiByte,
"gb": GByte,
"tib": TiByte,
"tb": TByte,
"pib": PiByte,
"pb": PByte,
"eib": EiByte,
"eb": EByte,
// Without suffix
"": Byte,
"ki": KiByte,
"k": KByte,
"mi": MiByte,
"m": MByte,
"gi": GiByte,
"g": GByte,
"ti": TiByte,
"t": TByte,
"pi": PiByte,
"p": PByte,
"ei": EiByte,
"e": EByte,
}
var memorysSizeTable = map[string]uint64{
"b": Byte,
"kb": KiByte,
"mb": MiByte,
"gb": GiByte,
"tb": TiByte,
"pb": PiByte,
"eb": EiByte,
"": Byte,
"k": KiByte,
"m": MiByte,
"g": GiByte,
"t": TiByte,
"p": PiByte,
"e": EiByte,
}
var (
defaultSizes = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
iSizes = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
)
func Bytes(s uint64) string {
return humanateBytes(s, 1000, defaultSizes)
}
func MemoryBytes(s uint64) string {
return humanateBytes(s, 1024, defaultSizes)
}
func IBytes(s uint64) string {
return humanateBytes(s, 1024, iSizes)
}
func logn(n, b float64) float64 {
return math.Log(n) / math.Log(b)
}
func humanateBytes(s uint64, base float64, sizes []string) string {
if s < 10 {
return fmt.Sprintf("%d B", s)
}
e := math.Floor(logn(float64(s), base))
suffix := sizes[int(e)]
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
f := "%.0f %s"
if val < 10 {
f = "%.1f %s"
}
return fmt.Sprintf(f, val, suffix)
}
func ParseBytes(s string) (uint64, error) {
return parseBytes0(s, defaultSizeTable)
}
func ParseMemoryBytes(s string) (uint64, error) {
return parseBytes0(s, memorysSizeTable)
}
func parseBytes0(s string, sizeTable map[string]uint64) (uint64, error) {
lastDigit := 0
hasComma := false
for _, r := range s {
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
break
}
if r == ',' {
hasComma = true
}
lastDigit++
}
num := s[:lastDigit]
if hasComma {
num = strings.Replace(num, ",", "", -1)
}
f, err := strconv.ParseFloat(num, 64)
if err != nil {
return 0, err
}
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
if m, ok := sizeTable[extra]; ok {
f *= float64(m)
if f >= math.MaxUint64 {
return 0, fmt.Errorf("too large: %v", s)
}
return uint64(f), nil
}
return 0, fmt.Errorf("unhandled size name: %v", extra)
}

View File

@@ -24,9 +24,9 @@ func applyDebugOptions(options option.DebugOptions) {
if options.TraceBack != "" {
debug.SetTraceback(options.TraceBack)
}
if options.MemoryLimit != 0 {
debug.SetMemoryLimit(int64(float64(options.MemoryLimit) / 1.5))
conntrack.MemoryLimit = uint64(options.MemoryLimit)
if options.MemoryLimit.Value() != 0 {
debug.SetMemoryLimit(int64(float64(options.MemoryLimit.Value()) / 1.5))
conntrack.MemoryLimit = options.MemoryLimit.Value()
}
if options.OOMKiller != nil {
conntrack.KillerEnabled = *options.OOMKiller

View File

@@ -7,9 +7,9 @@ import (
"runtime/debug"
"strings"
"github.com/sagernet/sing-box/common/humanize"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/byteformats"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
@@ -38,9 +38,9 @@ func applyDebugListenOption(options option.DebugOptions) {
runtime.ReadMemStats(&memStats)
var memObject badjson.JSONObject
memObject.Put("heap", humanize.MemoryBytes(memStats.HeapInuse))
memObject.Put("stack", humanize.MemoryBytes(memStats.StackInuse))
memObject.Put("idle", humanize.MemoryBytes(memStats.HeapIdle-memStats.HeapReleased))
memObject.Put("heap", byteformats.FormatMemoryBytes(memStats.HeapInuse))
memObject.Put("stack", byteformats.FormatMemoryBytes(memStats.StackInuse))
memObject.Put("idle", byteformats.FormatMemoryBytes(memStats.HeapIdle-memStats.HeapReleased))
memObject.Put("goroutines", runtime.NumGoroutine())
memObject.Put("rss", rusageMaxRSS())

View File

@@ -93,11 +93,12 @@ func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options
return nil, err
}
serverAddr := options.DNSServerAddressOptions.Build()
if !serverAddr.Addr.IsValid() {
return nil, E.New("invalid server address: ", serverAddr)
} else if serverAddr.Port == 0 {
if serverAddr.Port == 0 {
serverAddr.Port = 443
}
if !serverAddr.IsValid() {
return nil, E.New("invalid server address: ", serverAddr)
}
return NewHTTPSRaw(
dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTPS, tag, options.RemoteDNSServerOptions),
logger,

View File

@@ -89,11 +89,12 @@ func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options
return nil, err
}
serverAddr := options.DNSServerAddressOptions.Build()
if !serverAddr.Addr.IsValid() {
return nil, E.New("invalid server address: ", serverAddr)
} else if serverAddr.Port == 0 {
if serverAddr.Port == 0 {
serverAddr.Port = 443
}
if !serverAddr.IsValid() {
return nil, E.New("invalid server address: ", serverAddr)
}
return &HTTP3Transport{
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTP3, tag, options.RemoteDNSServerOptions),
logger: logger,

View File

@@ -56,11 +56,12 @@ func NewQUIC(ctx context.Context, logger log.ContextLogger, tag string, options
tlsConfig.SetNextProtos([]string{"doq"})
}
serverAddr := options.DNSServerAddressOptions.Build()
if !serverAddr.Addr.IsValid() {
return nil, E.New("invalid server address: ", serverAddr)
} else if serverAddr.Port == 0 {
if serverAddr.Port == 0 {
serverAddr.Port = 853
}
if !serverAddr.IsValid() {
return nil, E.New("invalid server address: ", serverAddr)
}
return &Transport{
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeQUIC, tag, options.RemoteDNSServerOptions),
ctx: ctx,

View File

@@ -38,11 +38,12 @@ func NewTCP(ctx context.Context, logger log.ContextLogger, tag string, options o
return nil, err
}
serverAddr := options.DNSServerAddressOptions.Build()
if !serverAddr.Addr.IsValid() {
return nil, E.New("invalid server address: ", serverAddr)
} else if serverAddr.Port == 0 {
if serverAddr.Port == 0 {
serverAddr.Port = 53
}
if !serverAddr.IsValid() {
return nil, E.New("invalid server address: ", serverAddr)
}
return &TCPTransport{
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTCP, tag, options),
dialer: transportDialer,

View File

@@ -54,11 +54,12 @@ func NewTLS(ctx context.Context, logger log.ContextLogger, tag string, options o
return nil, err
}
serverAddr := options.DNSServerAddressOptions.Build()
if !serverAddr.Addr.IsValid() {
return nil, E.New("invalid server address: ", serverAddr)
} else if serverAddr.Port == 0 {
if serverAddr.Port == 0 {
serverAddr.Port = 853
}
if !serverAddr.IsValid() {
return nil, E.New("invalid server address: ", serverAddr)
}
return &TLSTransport{
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTLS, tag, options.RemoteDNSServerOptions),
logger: logger,

View File

@@ -45,11 +45,12 @@ func NewUDP(ctx context.Context, logger log.ContextLogger, tag string, options o
return nil, err
}
serverAddr := options.DNSServerAddressOptions.Build()
if !serverAddr.Addr.IsValid() {
return nil, E.New("invalid server address: ", serverAddr)
} else if serverAddr.Port == 0 {
if serverAddr.Port == 0 {
serverAddr.Port = 53
}
if !serverAddr.IsValid() {
return nil, E.New("invalid server address: ", serverAddr)
}
return NewUDPRaw(logger, dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeUDP, tag, options), transportDialer, serverAddr), nil
}

View File

@@ -2,7 +2,7 @@
icon: material/alert-decagram
---
#### 1.12.0-beta.6
#### 1.12.0-beta.7
* Fixes and improvements

View File

@@ -42,16 +42,18 @@ AnyTLS padding scheme line array.
Default padding scheme:
```
stop=8
0=30-30
1=100-400
2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000
3=9-9,500-1000
4=500-1000
5=500-1000
6=500-1000
7=500-1000
```json
[
"stop=8",
"0=30-30",
"1=100-400",
"2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000",
"3=9-9,500-1000",
"4=500-1000",
"5=500-1000",
"6=500-1000",
"7=500-1000"
]
```
#### tls

View File

@@ -42,16 +42,18 @@ AnyTLS 填充方案行数组。
默认填充方案:
```
stop=8
0=30-30
1=100-400
2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000
3=9-9,500-1000
4=500-1000
5=500-1000
6=500-1000
7=500-1000
```json
[
"stop=8",
"0=30-30",
"1=100-400",
"2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000",
"3=9-9,500-1000",
"4=500-1000",
"5=500-1000",
"6=500-1000",
"7=500-1000"
]
```
#### tls

View File

@@ -7,10 +7,10 @@ import (
"strconv"
"time"
"github.com/sagernet/sing-box/common/humanize"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/locale"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common/byteformats"
)
var (
@@ -75,11 +75,11 @@ func Version() string {
}
func FormatBytes(length int64) string {
return humanize.Bytes(uint64(length))
return byteformats.FormatBytes(uint64(length))
}
func FormatMemoryBytes(length int64) string {
return humanize.MemoryBytes(uint64(length))
return byteformats.FormatMemoryBytes(uint64(length))
}
func FormatDuration(duration int64) string {

View File

@@ -26,13 +26,13 @@ require (
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb
github.com/sagernet/quic-go v0.49.0-beta.1
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.6.8-0.20250425035333-84184da91a3a
github.com/sagernet/sing v0.6.8-0.20250428152347-91813c8e3960
github.com/sagernet/sing-mux v0.3.1
github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76
github.com/sagernet/sing-shadowsocks v0.2.7
github.com/sagernet/sing-shadowsocks2 v0.2.0
github.com/sagernet/sing-shadowtls v0.2.1-0.20250316154757-6f9e732e5056
github.com/sagernet/sing-tun v0.6.5-0.20250412112220-15069fc1c20a
github.com/sagernet/sing-tun v0.6.6-0.20250428031943-0686f8c4f210
github.com/sagernet/sing-vmess v0.2.1
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
github.com/sagernet/tailscale v1.80.3-mod.4

View File

@@ -178,8 +178,8 @@ github.com/sagernet/quic-go v0.49.0-beta.1/go.mod h1:uesWD1Ihrldq1M3XtjuEvIUqi8W
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing v0.6.8-0.20250425035333-84184da91a3a h1:oE67hmp5rzLlE6clE7FpK4Hg6yLXsa1Zu3A01vcazb0=
github.com/sagernet/sing v0.6.8-0.20250425035333-84184da91a3a/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing v0.6.8-0.20250428152347-91813c8e3960 h1:bBSVJ0d5YtbhhwugBXH8GrVaKi1ZIHYdL03LN006Fng=
github.com/sagernet/sing v0.6.8-0.20250428152347-91813c8e3960/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-mux v0.3.1 h1:kvCc8HyGAskDHDQ0yQvoTi/7J4cZPB/VJMsAM3MmdQI=
github.com/sagernet/sing-mux v0.3.1/go.mod h1:Mkdz8LnDstthz0HWuA/5foncnDIdcNN5KZ6AdJX+x78=
github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76 h1:iwpCX6H3nZEOGUGwx0q5azcgYOA9f6v9YssihXoRKHk=
@@ -190,8 +190,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
github.com/sagernet/sing-shadowtls v0.2.1-0.20250316154757-6f9e732e5056 h1:GFNJQAHhSXqAfxAw1wDG/QWbdpGH5Na3k8qUynqWnEA=
github.com/sagernet/sing-shadowtls v0.2.1-0.20250316154757-6f9e732e5056/go.mod h1:HyacBPIFiKihJQR8LQp56FM4hBtd/7MZXnRxxQIOPsc=
github.com/sagernet/sing-tun v0.6.5-0.20250412112220-15069fc1c20a h1:2aLxZFD2HPCLrnFGpH+KBuPqMOk0cuaDE2dgEvANuMk=
github.com/sagernet/sing-tun v0.6.5-0.20250412112220-15069fc1c20a/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
github.com/sagernet/sing-tun v0.6.6-0.20250428031943-0686f8c4f210 h1:6H4BZaTqKI3YcDMyTV3E576LuJM4S4wY99xoq2T1ECw=
github.com/sagernet/sing-tun v0.6.6-0.20250428031943-0686f8c4f210/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
github.com/sagernet/sing-vmess v0.2.1 h1:6izHC2+B68aQCxTagki6eZZc+g5eh4dYwxOV5a2Lhug=
github.com/sagernet/sing-vmess v0.2.1/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=

View File

@@ -1,43 +1,14 @@
package option
import (
"github.com/sagernet/sing-box/common/humanize"
"github.com/sagernet/sing/common/json"
)
import "github.com/sagernet/sing/common/byteformats"
type DebugOptions struct {
Listen string `json:"listen,omitempty"`
GCPercent *int `json:"gc_percent,omitempty"`
MaxStack *int `json:"max_stack,omitempty"`
MaxThreads *int `json:"max_threads,omitempty"`
PanicOnFault *bool `json:"panic_on_fault,omitempty"`
TraceBack string `json:"trace_back,omitempty"`
MemoryLimit MemoryBytes `json:"memory_limit,omitempty"`
OOMKiller *bool `json:"oom_killer,omitempty"`
}
type MemoryBytes uint64
func (l MemoryBytes) MarshalJSON() ([]byte, error) {
return json.Marshal(humanize.MemoryBytes(uint64(l)))
}
func (l *MemoryBytes) UnmarshalJSON(bytes []byte) error {
var valueInteger int64
err := json.Unmarshal(bytes, &valueInteger)
if err == nil {
*l = MemoryBytes(valueInteger)
return nil
}
var valueString string
err = json.Unmarshal(bytes, &valueString)
if err != nil {
return err
}
parsedValue, err := humanize.ParseMemoryBytes(valueString)
if err != nil {
return err
}
*l = MemoryBytes(parsedValue)
return nil
Listen string `json:"listen,omitempty"`
GCPercent *int `json:"gc_percent,omitempty"`
MaxStack *int `json:"max_stack,omitempty"`
MaxThreads *int `json:"max_threads,omitempty"`
PanicOnFault *bool `json:"panic_on_fault,omitempty"`
TraceBack string `json:"trace_back,omitempty"`
MemoryLimit *byteformats.MemoryBytes `json:"memory_limit,omitempty"`
OOMKiller *bool `json:"oom_killer,omitempty"`
}

View File

@@ -1,19 +1,22 @@
package option
import "github.com/sagernet/sing/common/json/badoption"
import (
"github.com/sagernet/sing/common/byteformats"
"github.com/sagernet/sing/common/json/badoption"
)
type HysteriaInboundOptions struct {
ListenOptions
Up string `json:"up,omitempty"`
UpMbps int `json:"up_mbps,omitempty"`
Down string `json:"down,omitempty"`
DownMbps int `json:"down_mbps,omitempty"`
Obfs string `json:"obfs,omitempty"`
Users []HysteriaUser `json:"users,omitempty"`
ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"`
ReceiveWindowClient uint64 `json:"recv_window_client,omitempty"`
MaxConnClient int `json:"max_conn_client,omitempty"`
DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"`
Up *byteformats.NetworkBytesCompat `json:"up,omitempty"`
UpMbps int `json:"up_mbps,omitempty"`
Down *byteformats.NetworkBytesCompat `json:"down,omitempty"`
DownMbps int `json:"down_mbps,omitempty"`
Obfs string `json:"obfs,omitempty"`
Users []HysteriaUser `json:"users,omitempty"`
ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"`
ReceiveWindowClient uint64 `json:"recv_window_client,omitempty"`
MaxConnClient int `json:"max_conn_client,omitempty"`
DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"`
InboundTLSOptionsContainer
}
@@ -26,18 +29,18 @@ type HysteriaUser struct {
type HysteriaOutboundOptions struct {
DialerOptions
ServerOptions
ServerPorts badoption.Listable[string] `json:"server_ports,omitempty"`
HopInterval badoption.Duration `json:"hop_interval,omitempty"`
Up string `json:"up,omitempty"`
UpMbps int `json:"up_mbps,omitempty"`
Down string `json:"down,omitempty"`
DownMbps int `json:"down_mbps,omitempty"`
Obfs string `json:"obfs,omitempty"`
Auth []byte `json:"auth,omitempty"`
AuthString string `json:"auth_str,omitempty"`
ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"`
ReceiveWindow uint64 `json:"recv_window,omitempty"`
DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"`
Network NetworkList `json:"network,omitempty"`
ServerPorts badoption.Listable[string] `json:"server_ports,omitempty"`
HopInterval badoption.Duration `json:"hop_interval,omitempty"`
Up *byteformats.NetworkBytesCompat `json:"up,omitempty"`
UpMbps int `json:"up_mbps,omitempty"`
Down *byteformats.NetworkBytesCompat `json:"down,omitempty"`
DownMbps int `json:"down_mbps,omitempty"`
Obfs string `json:"obfs,omitempty"`
Auth []byte `json:"auth,omitempty"`
AuthString string `json:"auth_str,omitempty"`
ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"`
ReceiveWindow uint64 `json:"recv_window,omitempty"`
DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"`
Network NetworkList `json:"network,omitempty"`
OutboundTLSOptionsContainer
}

View File

@@ -7,7 +7,6 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/common/humanize"
"github.com/sagernet/sing-box/common/listener"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
@@ -16,7 +15,6 @@ import (
"github.com/sagernet/sing-quic/hysteria"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
@@ -56,19 +54,13 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
tlsConfig: tlsConfig,
}
var sendBps, receiveBps uint64
if len(options.Up) > 0 {
sendBps, err = humanize.ParseBytes(options.Up)
if err != nil {
return nil, E.Cause(err, "invalid up speed format: ", options.Up)
}
if options.Up.Value() > 0 {
sendBps = options.Up.Value()
} else {
sendBps = uint64(options.UpMbps) * hysteria.MbpsToBps
}
if len(options.Down) > 0 {
receiveBps, err = humanize.ParseBytes(options.Down)
if err != nil {
return nil, E.Cause(err, "invalid down speed format: ", options.Down)
}
if options.Down.Value() > 0 {
receiveBps = options.Down.Value()
} else {
receiveBps = uint64(options.DownMbps) * hysteria.MbpsToBps
}

View File

@@ -9,7 +9,6 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/outbound"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/humanize"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
@@ -60,19 +59,13 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
password = string(options.Auth)
}
var sendBps, receiveBps uint64
if len(options.Up) > 0 {
sendBps, err = humanize.ParseBytes(options.Up)
if err != nil {
return nil, E.Cause(err, "invalid up speed format: ", options.Up)
}
if options.Up.Value() > 0 {
sendBps = options.Up.Value()
} else {
sendBps = uint64(options.UpMbps) * hysteria.MbpsToBps
}
if len(options.Down) > 0 {
receiveBps, err = humanize.ParseBytes(options.Down)
if err != nil {
return nil, E.Cause(err, "invalid down speed format: ", options.Down)
}
if options.Down.Value() > 0 {
receiveBps = options.Down.Value()
} else {
receiveBps = uint64(options.DownMbps) * hysteria.MbpsToBps
}

View File

@@ -9,6 +9,7 @@ local split = api.split
local local_version = api.get_app_version("sing-box")
local version_ge_1_11_0 = api.compare_versions(local_version:match("[^v]+"), ">=", "1.11.0")
local version_ge_1_12_0 = api.compare_versions(local_version:match("[^v]+"), ">=", "1.12.0")
local geosite_all_tag = {}
local geoip_all_tag = {}
@@ -126,6 +127,15 @@ function gen_outbound(flag, node, tag, proxy_table)
detour = node.detour,
}
if version_ge_1_12_0 then
--https://sing-box.sagernet.org/migration/#migrate-outbound-domain-strategy-option-to-domain-resolver
result.domain_strategy = nil
result.domain_resolver = {
server = "direct",
strategy = (node.domain_strategy and node.domain_strategy ~="") and node.domain_strategy or nil
}
end
local tls = nil
if node.tls == "1" then
local alpn = nil
@@ -841,6 +851,7 @@ function gen_config(var)
local direct_dns_tcp_server = var["-direct_dns_tcp_server"]
local direct_dns_dot_server = var["-direct_dns_dot_server"]
local direct_dns_query_strategy = var["-direct_dns_query_strategy"]
local remote_dns_server = var["-remote_dns_server"]
local remote_dns_port = var["-remote_dns_port"]
local remote_dns_udp_server = var["-remote_dns_udp_server"]
local remote_dns_tcp_server = var["-remote_dns_tcp_server"]
@@ -1420,10 +1431,12 @@ function gen_config(var)
fakeip = nil,
}
table.insert(dns.servers, {
tag = "block",
address = "rcode://success",
})
if not version_ge_1_12_0 then --Migrate to new DNS server formats
table.insert(dns.servers, {
tag = "block",
address = "rcode://success",
})
end
if dns_socks_address and dns_socks_port then
default_outTag = "dns_socks_out"
@@ -1444,57 +1457,118 @@ function gen_config(var)
remote_strategy = "ipv6_only"
end
local remote_server = {
tag = "remote",
address_strategy = "prefer_ipv4",
strategy = remote_strategy,
address_resolver = "direct",
detour = default_outTag,
client_subnet = (remote_dns_client_ip and remote_dns_client_ip ~= "") and remote_dns_client_ip or nil,
}
if remote_dns_udp_server then
local server_port = tonumber(remote_dns_port) or 53
remote_server.address = "udp://" .. remote_dns_udp_server .. ":" .. server_port
end
if remote_dns_tcp_server then
remote_server.address = remote_dns_tcp_server
end
if remote_dns_doh_url and remote_dns_doh_host then
remote_server.address = remote_dns_doh_url
end
if remote_server.address then
table.insert(dns.servers, remote_server)
end
local remote_server = {}
local fakedns_tag = "remote_fakeip"
if remote_dns_fake then
dns.fakeip = {
enabled = true,
inet4_range = "198.18.0.0/15",
inet6_range = "fc00::/18",
}
table.insert(dns.servers, {
tag = fakedns_tag,
address = "fakeip",
strategy = remote_strategy,
})
if not experimental then
experimental = {}
end
experimental.cache_file = {
enabled = true,
store_fakeip = true,
path = api.CACHE_PATH .. "/singbox_" .. flag .. ".db"
if not version_ge_1_12_0 then --Migrate to new DNS server formats
remote_server = {
tag = "remote",
address_strategy = "prefer_ipv4",
strategy = remote_strategy,
address_resolver = "direct",
detour = default_outTag,
client_subnet = (remote_dns_client_ip and remote_dns_client_ip ~= "") and remote_dns_client_ip or nil,
}
if remote_dns_udp_server then
local server_port = tonumber(remote_dns_port) or 53
remote_server.address = "udp://" .. remote_dns_udp_server .. ":" .. server_port
end
if remote_dns_tcp_server then
remote_server.address = remote_dns_tcp_server
end
if remote_dns_doh_url and remote_dns_doh_host then
remote_server.address = remote_dns_doh_url
end
if remote_server.address then
table.insert(dns.servers, remote_server)
end
if remote_dns_fake then
dns.fakeip = {
enabled = true,
inet4_range = "198.18.0.0/15",
inet6_range = "fc00::/18",
}
table.insert(dns.servers, {
tag = fakedns_tag,
address = "fakeip",
strategy = remote_strategy,
})
if not experimental then
experimental = {}
end
experimental.cache_file = {
enabled = true,
store_fakeip = true,
path = api.CACHE_PATH .. "/singbox_" .. flag .. ".db"
}
end
else -- Migrate to 1.12 DNS
remote_server = {
tag = "remote",
domain_strategy = remote_strategy,
domain_resolver = "direct",
detour = default_outTag,
}
if remote_dns_udp_server then
local server_port = tonumber(remote_dns_port) or 53
remote_server.type = "udp"
remote_server.server = remote_dns_udp_server
remote_server.server_port = server_port
end
if remote_dns_tcp_server then
local server_port = tonumber(remote_dns_port) or 53
remote_server.type = "tcp"
remote_server.server = remote_dns_server
remote_server.server_port = server_port
end
if remote_dns_doh_url and remote_dns_doh_host then
local server_port = tonumber(remote_dns_port) or 443
remote_server.type = "https"
remote_server.server = remote_dns_doh_host
remote_server.server_port = server_port
end
if remote_server.server then
table.insert(dns.servers, remote_server)
end
if remote_dns_fake then
table.insert(dns.servers, {
tag = fakedns_tag,
type = "fakeip",
inet4_range = "198.18.0.0/15",
inet6_range = "fc00::/18",
})
if not experimental then
experimental = {}
end
experimental.cache_file = {
enabled = true,
store_fakeip = true,
path = api.CACHE_PATH .. "/singbox_" .. flag .. ".db"
}
end
end
local direct_strategy = "prefer_ipv6"
if direct_dns_udp_server or direct_dns_tcp_server or direct_dns_dot_server then
if direct_dns_query_strategy == "UseIPv4" then
direct_strategy = "ipv4_only"
elseif direct_dns_query_strategy == "UseIPv6" then
direct_strategy = "ipv6_only"
end
local domain = {}
local nodes_domain_text = sys.exec('uci show passwall | grep ".address=" | cut -d "\'" -f 2 | grep "[a-zA-Z]$" | sort -u')
string.gsub(nodes_domain_text, '[^' .. "\r\n" .. ']+', function(w)
@@ -1503,40 +1577,61 @@ function gen_config(var)
if #domain > 0 then
table.insert(dns_domain_rules, 1, {
outboundTag = "direct",
domain = domain
domain = domain,
strategy = version_ge_1_12_0 and direct_strategy or nil
})
end
local direct_strategy = "prefer_ipv6"
if direct_dns_query_strategy == "UseIPv4" then
direct_strategy = "ipv4_only"
elseif direct_dns_query_strategy == "UseIPv6" then
direct_strategy = "ipv6_only"
if not version_ge_1_12_0 then --Migrate to new DNS server formats
local direct_dns_server, port
if direct_dns_udp_server then
port = tonumber(direct_dns_port) or 53
direct_dns_server = "udp://" .. direct_dns_udp_server .. ":" .. port
elseif direct_dns_tcp_server then
port = tonumber(direct_dns_port) or 53
direct_dns_server = "tcp://" .. direct_dns_tcp_server .. ":" .. port
elseif direct_dns_dot_server then
port = tonumber(direct_dns_port) or 853
if direct_dns_dot_server:find(":") == nil then
direct_dns_server = "tls://" .. direct_dns_dot_server .. ":" .. port
else
direct_dns_server = "tls://[" .. direct_dns_dot_server .. "]:" .. port
end
end
table.insert(dns.servers, {
tag = "direct",
address = direct_dns_server,
address_strategy = "prefer_ipv6",
strategy = direct_strategy,
detour = "direct",
})
else -- Migrate to 1.12 DNS
local direct_dns_server, port, type
if direct_dns_udp_server then
port = tonumber(direct_dns_port) or 53
direct_dns_server = direct_dns_udp_server
type = "udp"
elseif direct_dns_tcp_server then
port = tonumber(direct_dns_port) or 53
direct_dns_server = direct_dns_tcp_server
type = "tcp"
elseif direct_dns_dot_server then
port = tonumber(direct_dns_port) or 853
direct_dns_server = direct_dns_dot_server
type = "tls"
end
table.insert(dns.servers, {
tag = "direct",
type = type,
server = direct_dns_server,
server_port = port,
domain_strategy = direct_strategy,
detour = "direct",
})
end
local direct_dns_server, port
if direct_dns_udp_server then
port = tonumber(direct_dns_port) or 53
direct_dns_server = "udp://" .. direct_dns_udp_server .. ":" .. port
elseif direct_dns_tcp_server then
port = tonumber(direct_dns_port) or 53
direct_dns_server = "tcp://" .. direct_dns_tcp_server .. ":" .. port
elseif direct_dns_dot_server then
port = tonumber(direct_dns_port) or 853
if direct_dns_dot_server:find(":") == nil then
direct_dns_server = "tls://" .. direct_dns_dot_server .. ":" .. port
else
direct_dns_server = "tls://[" .. direct_dns_dot_server .. "]:" .. port
end
end
table.insert(dns.servers, {
tag = "direct",
address = direct_dns_server,
address_strategy = "prefer_ipv6",
strategy = direct_strategy,
detour = "direct",
})
end
local default_dns_flag = "remote"
@@ -1561,6 +1656,9 @@ function gen_config(var)
end
end
dns.final = default_dns_flag
if version_ge_1_12_0 then -- Migrate to 1.12 DNS
dns.strategy = (default_dns_flag == "direct") and direct_strategy or remote_strategy
end
--按分流顺序DNS
if dns_domain_rules and #dns_domain_rules > 0 then
@@ -1574,15 +1672,24 @@ function gen_config(var)
domain_regex = (value.domain_regex and #value.domain_regex > 0) and value.domain_regex or nil,
rule_set = (value.rule_set and #value.rule_set > 0) and value.rule_set or nil, --适配srs
disable_cache = false,
strategy = (version_ge_1_12_0 and value.outboundTag == "direct") and direct_strategy or nil --Migrate to 1.12 DNS
}
if version_ge_1_12_0 and value.outboundTag == "block" then --Migrate to 1.12 DNS
dns_rule.action = "predefined"
dns_rule.rcode = "NOERROR"
dns_rule.server = nil
dns_rule.disable_cache = nil
end
if value.outboundTag ~= "block" and value.outboundTag ~= "direct" then
dns_rule.server = "remote"
if value.outboundTag ~= COMMON.default_outbound_tag and remote_server.address then
local remote_dns_server = api.clone(remote_server)
remote_dns_server.tag = value.outboundTag
remote_dns_server.detour = value.outboundTag
table.insert(dns.servers, remote_dns_server)
dns_rule.server = remote_dns_server.tag
dns_rule.strategy = version_ge_1_12_0 and remote_strategy or nil --Migrate to 1.12 DNS
dns_rule.client_subnet = (version_ge_1_12_0 and remote_dns_client_ip and remote_dns_client_ip ~= "") and remote_dns_client_ip or nil --Migrate to 1.12 DNS
if value.outboundTag ~= COMMON.default_outbound_tag and (remote_server.address or remote_server.server) then
local remote_shunt_server = api.clone(remote_server)
remote_shunt_server.tag = value.outboundTag
remote_shunt_server.detour = value.outboundTag
table.insert(dns.servers, remote_shunt_server)
dns_rule.server = remote_shunt_server.tag
end
if remote_dns_fake then
local fakedns_dns_rule = api.clone(dns_rule)
@@ -1638,12 +1745,44 @@ function gen_config(var)
--实验性
experimental = experimental,
}
table.insert(outbounds, {
local direct_outbound = {
type = "direct",
tag = "direct",
routing_mark = 255,
domain_strategy = "prefer_ipv6",
})
}
if not version_ge_1_12_0 then --Migrate to 1.12 DNS
direct_outbound.domain_strategy = "prefer_ipv6"
else
local domain_resolver = {
server = "direct",
strategy = "prefer_ipv6"
}
direct_outbound.domain_resolver = domain_resolver
-- 当没有 direct dns 服务器时添加 local
local hasDirect = false
if config.dns and config.dns.servers then
for _, server in ipairs(config.dns.servers) do
if server.tag == "direct" then
hasDirect = true
break
end
end
end
if not hasDirect then
config.dns = {
servers = {
{
type = "local",
tag = "direct"
}
},
}
end
end
table.insert(outbounds,direct_outbound)
table.insert(outbounds, {
type = "block",
tag = "block"

View File

@@ -6,12 +6,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=sing-box
PKG_VERSION:=1.11.8
PKG_VERSION:=1.11.9
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/SagerNet/sing-box/tar.gz/v$(PKG_VERSION)?
PKG_HASH:=de797c9e848c9c3a6f99cd779b442a4e856a7f267e514cfc8169f95baf48dadd
PKG_HASH:=ee0c183ed9c431a2b6b5f12cad0cfb1d2bccb83147b641d50a619a381fa9d449
PKG_LICENSE:=GPL-3.0-or-later
PKG_LICENSE_FILES:=LICENSE

View File

@@ -21,22 +21,22 @@ define Download/geoip
HASH:=735786c00694313090c5d525516463836167422b132ce293873443613b496e92
endef
GEOSITE_VER:=20250422055726
GEOSITE_VER:=20250428010409
GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER)
define Download/geosite
URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/
URL_FILE:=dlc.dat
FILE:=$(GEOSITE_FILE)
HASH:=7a4530370eff2db6265613d9066c2b5eee841e3e7f02288750b03128af6daf7b
HASH:=bb0542801286c3efca59305b8fb1b01e808535be3ce1331a9f8709704417ea7d
endef
GEOSITE_IRAN_VER:=202504210040
GEOSITE_IRAN_VER:=202504280040
GEOSITE_IRAN_FILE:=iran.dat.$(GEOSITE_IRAN_VER)
define Download/geosite-ir
URL:=https://github.com/bootmortis/iran-hosted-domains/releases/download/$(GEOSITE_IRAN_VER)/
URL_FILE:=iran.dat
FILE:=$(GEOSITE_IRAN_FILE)
HASH:=2d10e5a69771bc7daf612ce31e9dfc2dfd7ea6caa9caecfd88918da0ebd9cba7
HASH:=fdad880cfe7dd8bf94e930e2950dd6f8609e2e8543287f411ebe27ec9da4b2c6
endef
define Package/v2ray-geodata/template

View File

@@ -18,7 +18,7 @@ import (
)
var (
version = "5.30.0"
version = "5.31.0"
build = "Custom"
codename = "V2Fly, a community-driven edition of V2Ray."
intro = "A unified platform for anti-censorship."

View File

@@ -1,6 +1,6 @@
module github.com/v2fly/v2ray-core/v5
go 1.23.0
go 1.24
toolchain go1.24.2
@@ -24,8 +24,8 @@ require (
github.com/pion/dtls/v2 v2.2.12
github.com/pion/transport/v2 v2.2.10
github.com/pires/go-proxyproto v0.8.0
github.com/quic-go/quic-go v0.50.1
github.com/refraction-networking/utls v1.6.7
github.com/quic-go/quic-go v0.51.0
github.com/refraction-networking/utls v1.7.1
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb
github.com/stretchr/testify v1.10.0
github.com/v2fly/BrowserBridge v0.0.0-20210430233438-0570fc1d7d08
@@ -41,7 +41,7 @@ require (
golang.org/x/net v0.39.0
golang.org/x/sync v0.13.0
golang.org/x/sys v0.32.0
google.golang.org/grpc v1.71.1
google.golang.org/grpc v1.72.0
google.golang.org/protobuf v1.36.6
gopkg.in/yaml.v3 v3.0.1
gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1
@@ -56,7 +56,7 @@ require (
github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d // indirect
github.com/bufbuild/protocompile v0.14.1 // indirect
github.com/cenkalti/backoff/v4 v4.1.1 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/cloudflare/circl v1.5.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
@@ -92,6 +92,6 @@ require (
golang.org/x/text v0.24.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.30.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
nhooyr.io/websocket v1.8.6 // indirect
)

View File

@@ -65,8 +65,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
@@ -442,11 +442,11 @@ github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.50.1 h1:unsgjFIUqW8a2oopkY7YNONpV1gYND6Nt9hnt1PN94Q=
github.com/quic-go/quic-go v0.50.1/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E=
github.com/quic-go/quic-go v0.51.0 h1:K8exxe9zXxeRKxaXxi/GpUqYiTrtdiWP8bo1KFya6Wc=
github.com/quic-go/quic-go v0.51.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
github.com/refraction-networking/utls v1.7.1 h1:dxg+jla3uocgN8HtX+ccwDr68uCBBO3qLrkZUbqkcw0=
github.com/refraction-networking/utls v1.7.1/go.mod h1:TUhh27RHMGtQvjQq+RyO11P6ZNQNBb3N0v7wsEjKAIQ=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
@@ -807,8 +807,8 @@ google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvx
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
@@ -823,8 +823,8 @@ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=

View File

@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<Version>7.11.3</Version>
<Version>7.12.0</Version>
</PropertyGroup>
<PropertyGroup>

View File

@@ -27,4 +27,4 @@
<PackageVersion Include="YamlDotNet" Version="16.3.0" />
<PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.14" />
</ItemGroup>
</Project>
</Project>

View File

@@ -9,7 +9,6 @@ public sealed class AppHandler
private int? _statePort;
private int? _statePort2;
private Job? _processJob;
private bool? _isAdministrator;
public static AppHandler Instance => _instance.Value;
public Config Config => _config;

View File

@@ -10,7 +10,6 @@ public class CoreAdminHandler
public static CoreAdminHandler Instance => _instance.Value;
private Config _config;
private Action<bool, string>? _updateFunc;
private const string _tag = "CoreAdminHandler";
private int _linuxSudoPid = -1;
public async Task Init(Config config, Action<bool, string> updateFunc)
@@ -30,69 +29,60 @@ public class CoreAdminHandler
public async Task<Process?> RunProcessAsLinuxSudo(string fileName, CoreInfo coreInfo, string configPath)
{
try
var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath).AppendQuotes())}";
var shFilePath = await CreateLinuxShellFile(cmdLine, "run_as_sudo.sh");
Process proc = new()
{
var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath).AppendQuotes())}";
var shFilePath = await CreateLinuxShellFile(cmdLine, "run_as_sudo.sh");
Process proc = new()
StartInfo = new()
{
StartInfo = new()
{
FileName = shFilePath,
Arguments = "",
WorkingDirectory = Utils.GetBinConfigPath(),
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
StandardInputEncoding = Encoding.UTF8,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8,
}
};
proc.OutputDataReceived += (sender, e) =>
{
if (e.Data.IsNotEmpty())
{
UpdateFunc(false, e.Data + Environment.NewLine);
}
};
proc.ErrorDataReceived += (sender, e) =>
{
if (e.Data.IsNotEmpty())
{
UpdateFunc(false, e.Data + Environment.NewLine);
}
};
proc.Start();
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
await Task.Delay(10);
await proc.StandardInput.WriteLineAsync();
await Task.Delay(10);
await proc.StandardInput.WriteLineAsync(AppHandler.Instance.LinuxSudoPwd);
await Task.Delay(100);
if (proc is null or { HasExited: true })
{
throw new Exception(ResUI.FailedToRunCore);
FileName = shFilePath,
Arguments = "",
WorkingDirectory = Utils.GetBinConfigPath(),
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
StandardInputEncoding = Encoding.UTF8,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8,
}
};
_linuxSudoPid = proc.Id;
return proc;
}
catch (Exception ex)
proc.OutputDataReceived += (sender, e) =>
{
Logging.SaveLog(_tag, ex);
UpdateFunc(false, ex.Message);
return null;
if (e.Data.IsNotEmpty())
{
UpdateFunc(false, e.Data + Environment.NewLine);
}
};
proc.ErrorDataReceived += (sender, e) =>
{
if (e.Data.IsNotEmpty())
{
UpdateFunc(false, e.Data + Environment.NewLine);
}
};
proc.Start();
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
await Task.Delay(10);
await proc.StandardInput.WriteLineAsync();
await Task.Delay(10);
await proc.StandardInput.WriteLineAsync(AppHandler.Instance.LinuxSudoPwd);
await Task.Delay(100);
if (proc is null or { HasExited: true })
{
throw new Exception(ResUI.FailedToRunCore);
}
_linuxSudoPid = proc.Id;
return proc;
}
public async Task KillProcessAsLinuxSudo()
@@ -102,22 +92,14 @@ public class CoreAdminHandler
return;
}
try
{
var cmdLine = $"pkill -P {_linuxSudoPid} ; kill {_linuxSudoPid}";
var shFilePath = await CreateLinuxShellFile(cmdLine, "kill_as_sudo.sh");
var cmdLine = $"pkill -P {_linuxSudoPid} ; kill {_linuxSudoPid}";
var shFilePath = await CreateLinuxShellFile(cmdLine, "kill_as_sudo.sh");
var result = await Cli.Wrap(shFilePath)
.WithStandardInputPipe(PipeSource.FromString(AppHandler.Instance.LinuxSudoPwd))
.ExecuteAsync();
await Cli.Wrap(shFilePath)
.WithStandardInputPipe(PipeSource.FromString(AppHandler.Instance.LinuxSudoPwd))
.ExecuteAsync();
_linuxSudoPid = -1;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
UpdateFunc(false, ex.Message);
}
_linuxSudoPid = -1;
}
private async Task<string> CreateLinuxShellFile(string cmdLine, string fileName)

View File

@@ -238,66 +238,19 @@ public class CoreHandler
return null;
}
if (mayNeedSudo
&& _config.TunModeItem.EnableTun
&& coreInfo.CoreType == ECoreType.sing_box
&& Utils.IsNonWindows())
{
_linuxSudo = true;
await CoreAdminHandler.Instance.Init(_config, _updateFunc);
return await CoreAdminHandler.Instance.RunProcessAsLinuxSudo(fileName, coreInfo, configPath);
}
try
{
Process proc = new()
if (mayNeedSudo
&& _config.TunModeItem.EnableTun
&& coreInfo.CoreType == ECoreType.sing_box
&& Utils.IsNonWindows())
{
StartInfo = new()
{
FileName = fileName,
Arguments = string.Format(coreInfo.Arguments, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath),
WorkingDirectory = Utils.GetBinConfigPath(),
UseShellExecute = false,
RedirectStandardOutput = displayLog,
RedirectStandardError = displayLog,
CreateNoWindow = true,
StandardOutputEncoding = displayLog ? Encoding.UTF8 : null,
StandardErrorEncoding = displayLog ? Encoding.UTF8 : null,
}
};
if (displayLog)
{
proc.OutputDataReceived += (sender, e) =>
{
if (e.Data.IsNotEmpty())
{
UpdateFunc(false, e.Data + Environment.NewLine);
}
};
proc.ErrorDataReceived += (sender, e) =>
{
if (e.Data.IsNotEmpty())
{
UpdateFunc(false, e.Data + Environment.NewLine);
}
};
}
proc.Start();
if (displayLog)
{
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
_linuxSudo = true;
await CoreAdminHandler.Instance.Init(_config, _updateFunc);
return await CoreAdminHandler.Instance.RunProcessAsLinuxSudo(fileName, coreInfo, configPath);
}
await Task.Delay(500);
AppHandler.Instance.AddProcess(proc.Handle);
if (proc is null or { HasExited: true })
{
throw new Exception(ResUI.FailedToRunCore);
}
return proc;
return await RunProcessNormal(fileName, coreInfo, configPath, displayLog);
}
catch (Exception ex)
{
@@ -307,5 +260,57 @@ public class CoreHandler
}
}
private async Task<Process?> RunProcessNormal(string fileName, CoreInfo? coreInfo, string configPath, bool displayLog)
{
Process proc = new()
{
StartInfo = new()
{
FileName = fileName,
Arguments = string.Format(coreInfo.Arguments, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath),
WorkingDirectory = Utils.GetBinConfigPath(),
UseShellExecute = false,
RedirectStandardOutput = displayLog,
RedirectStandardError = displayLog,
CreateNoWindow = true,
StandardOutputEncoding = displayLog ? Encoding.UTF8 : null,
StandardErrorEncoding = displayLog ? Encoding.UTF8 : null,
}
};
if (displayLog)
{
proc.OutputDataReceived += (sender, e) =>
{
if (e.Data.IsNotEmpty())
{
UpdateFunc(false, e.Data + Environment.NewLine);
}
};
proc.ErrorDataReceived += (sender, e) =>
{
if (e.Data.IsNotEmpty())
{
UpdateFunc(false, e.Data + Environment.NewLine);
}
};
}
proc.Start();
if (displayLog)
{
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
}
await Task.Delay(100);
AppHandler.Instance.AddProcess(proc.Handle);
if (proc is null or { HasExited: true })
{
throw new Exception(ResUI.FailedToRunCore);
}
return proc;
}
#endregion Process
}

View File

@@ -18,6 +18,9 @@
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter" />
<conv:DelayColorConverter x:Key="DelayColorConverter" />
<Style x:Key="AccessibleMyChipListBoxItem" TargetType="ListBoxItem" BasedOn="{StaticResource MyChipListBoxItem}">
<Setter Property="AutomationProperties.Name" Value="{Binding Remarks}" />
</Style>
</UserControl.Resources>
<Grid>
<DockPanel>
@@ -26,8 +29,9 @@
x:Name="lstGroup"
MaxHeight="200"
FontSize="{DynamicResource StdFontSize}"
ItemContainerStyle="{StaticResource MyChipListBoxItem}"
Style="{StaticResource MaterialDesignChoiceChipPrimaryOutlineListBox}">
ItemContainerStyle="{StaticResource AccessibleMyChipListBoxItem}"
Style="{StaticResource MaterialDesignChoiceChipPrimaryOutlineListBox}"
AutomationProperties.Name="{x:Static resx:ResUI.menuSubscription}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Remarks}" />

View File

@@ -92,7 +92,13 @@
AutomationProperties.Name="{x:Static resx:ResUI.menuRouting}"
DisplayMemberPath="Remarks"
FontSize="{DynamicResource StdFontSize}"
Style="{StaticResource MaterialDesignFloatingHintComboBox}" />
Style="{StaticResource MaterialDesignFloatingHintComboBox}">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="AutomationProperties.Name" Value="{Binding Remarks}" />
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</StackPanel>
<StackPanel Margin="{StaticResource MarginLeftRight8}" VerticalAlignment="Center">
@@ -184,7 +190,13 @@
AutomationProperties.Name="{x:Static resx:ResUI.menuRouting}"
DisplayMemberPath="Remarks"
FontSize="{DynamicResource StdFontSize}"
Style="{StaticResource MaterialDesignFilledComboBox}" />
Style="{StaticResource MaterialDesignFilledComboBox}">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="AutomationProperties.Name" Value="{Binding Remarks}" />
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</DockPanel>
</MenuItem.Header>
</MenuItem>
@@ -198,7 +210,13 @@
AutomationProperties.Name="{x:Static resx:ResUI.menuServers}"
DisplayMemberPath="Text"
FontSize="{DynamicResource StdFontSize}"
Style="{StaticResource MaterialDesignFilledComboBox}" />
Style="{StaticResource MaterialDesignFilledComboBox}">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="AutomationProperties.Name" Value="{Binding Text}" />
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</DockPanel>
</MenuItem.Header>
</MenuItem>

View File

@@ -97,7 +97,7 @@
}
],
"routing": {
"domainStrategy": "IPIfNonMatch",
"domainStrategy": "AsIs",
"rules": []
},
"dns": {

View File

@@ -286,7 +286,7 @@ object V2rayConfigManager {
v2rayConfig.routing.domainStrategy =
MmkvManager.decodeSettingsString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY)
?: "IPIfNonMatch"
?: "AsIs"
val rulesetItems = MmkvManager.decodeRoutingRulesets()
rulesetItems?.forEach { key ->

View File

@@ -33,23 +33,21 @@ type cachedReader struct {
cache buf.MultiBuffer
}
func (r *cachedReader) Cache(b *buf.Buffer) {
mb, _ := r.reader.ReadMultiBufferTimeout(time.Millisecond * 100)
func (r *cachedReader) Cache(b *buf.Buffer, deadline time.Duration) error {
mb, err := r.reader.ReadMultiBufferTimeout(deadline)
if err != nil {
return err
}
r.Lock()
if !mb.IsEmpty() {
r.cache, _ = buf.MergeMulti(r.cache, mb)
}
cacheLen := r.cache.Len()
if cacheLen <= b.Cap() {
b.Clear()
} else {
b.Release()
*b = *buf.NewWithSize(cacheLen)
}
rawBytes := b.Extend(cacheLen)
b.Clear()
rawBytes := b.Extend(b.Cap())
n := r.cache.Copy(rawBytes)
b.Resize(0, int32(n))
r.Unlock()
return nil
}
func (r *cachedReader) readInternal() buf.MultiBuffer {
@@ -355,7 +353,7 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De
}
func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, network net.Network) (SniffResult, error) {
payload := buf.New()
payload := buf.NewWithSize(32767)
defer payload.Release()
sniffer := NewSniffer(ctx)
@@ -367,26 +365,33 @@ func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, netw
}
contentResult, contentErr := func() (SniffResult, error) {
cacheDeadline := 200 * time.Millisecond
totalAttempt := 0
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
totalAttempt++
if totalAttempt > 2 {
return nil, errSniffingTimeout
}
cachingStartingTimeStamp := time.Now()
cacheErr := cReader.Cache(payload, cacheDeadline)
cachingTimeElapsed := time.Since(cachingStartingTimeStamp)
cacheDeadline -= cachingTimeElapsed
cReader.Cache(payload)
if !payload.IsEmpty() {
result, err := sniffer.Sniff(ctx, payload.Bytes(), network)
if err != common.ErrNoClue {
switch err {
case common.ErrNoClue: // No Clue: protocol not matches, and sniffer cannot determine whether there will be a match or not
totalAttempt++
case protocol.ErrProtoNeedMoreData: // Protocol Need More Data: protocol matches, but need more data to complete sniffing
if cacheErr != nil { // Cache error (e.g. timeout) counts for failed attempt
totalAttempt++
}
default:
return result, err
}
}
if payload.IsFull() {
return nil, errUnknownContent
if totalAttempt >= 2 || cacheDeadline <= 0 {
return nil, errSniffingTimeout
}
}
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/protocol/bittorrent"
"github.com/xtls/xray-core/common/protocol/http"
"github.com/xtls/xray-core/common/protocol/quic"
@@ -58,14 +59,17 @@ var errUnknownContent = errors.New("unknown content")
func (s *Sniffer) Sniff(c context.Context, payload []byte, network net.Network) (SniffResult, error) {
var pendingSniffer []protocolSnifferWithMetadata
for _, si := range s.sniffer {
s := si.protocolSniffer
protocolSniffer := si.protocolSniffer
if si.metadataSniffer || si.network != network {
continue
}
result, err := s(c, payload)
result, err := protocolSniffer(c, payload)
if err == common.ErrNoClue {
pendingSniffer = append(pendingSniffer, si)
continue
} else if err == protocol.ErrProtoNeedMoreData { // Sniffer protocol matched, but need more data to complete sniffing
s.sniffer = []protocolSnifferWithMetadata{si}
return nil, err
}
if err == nil && result != nil {

View File

@@ -15,6 +15,15 @@ const (
var pool = bytespool.GetPool(Size)
// ownership represents the data owner of the buffer.
type ownership uint8
const (
managed ownership = iota
unmanaged
bytespools
)
// Buffer is a recyclable allocation of a byte array. Buffer.Release() recycles
// the buffer into an internal buffer pool, in order to recreate a buffer more
// quickly.
@@ -22,11 +31,11 @@ type Buffer struct {
v []byte
start int32
end int32
unmanaged bool
ownership ownership
UDP *net.Destination
}
// New creates a Buffer with 0 length and 8K capacity.
// New creates a Buffer with 0 length and 8K capacity, managed.
func New() *Buffer {
buf := pool.Get().([]byte)
if cap(buf) >= Size {
@@ -40,7 +49,7 @@ func New() *Buffer {
}
}
// NewExisted creates a managed, standard size Buffer with an existed bytearray
// NewExisted creates a standard size Buffer with an existed bytearray, managed.
func NewExisted(b []byte) *Buffer {
if cap(b) < Size {
panic("Invalid buffer")
@@ -57,16 +66,16 @@ func NewExisted(b []byte) *Buffer {
}
}
// FromBytes creates a Buffer with an existed bytearray
// FromBytes creates a Buffer with an existed bytearray, unmanaged.
func FromBytes(b []byte) *Buffer {
return &Buffer{
v: b,
end: int32(len(b)),
unmanaged: true,
ownership: unmanaged,
}
}
// StackNew creates a new Buffer object on stack.
// StackNew creates a new Buffer object on stack, managed.
// This method is for buffers that is released in the same function.
func StackNew() Buffer {
buf := pool.Get().([]byte)
@@ -81,9 +90,17 @@ func StackNew() Buffer {
}
}
// NewWithSize creates a Buffer with 0 length and capacity with at least the given size, bytespool's.
func NewWithSize(size int32) *Buffer {
return &Buffer{
v: bytespool.Alloc(size),
ownership: bytespools,
}
}
// Release recycles the buffer into an internal buffer pool.
func (b *Buffer) Release() {
if b == nil || b.v == nil || b.unmanaged {
if b == nil || b.v == nil || b.ownership == unmanaged {
return
}
@@ -91,8 +108,13 @@ func (b *Buffer) Release() {
b.v = nil
b.Clear()
if cap(p) == Size {
pool.Put(p)
switch b.ownership {
case managed:
if cap(p) == Size {
pool.Put(p)
}
case bytespools:
bytespool.Free(p)
}
b.UDP = nil
}
@@ -215,13 +237,6 @@ func (b *Buffer) Cap() int32 {
return int32(len(b.v))
}
// NewWithSize creates a Buffer with 0 length and capacity with at least the given size.
func NewWithSize(size int32) *Buffer {
return &Buffer{
v: bytespool.Alloc(size),
}
}
// IsEmpty returns true if the buffer is empty.
func (b *Buffer) IsEmpty() bool {
return b.Len() == 0

View File

@@ -1 +1,7 @@
package protocol // import "github.com/xtls/xray-core/common/protocol"
import (
"errors"
)
var ErrProtoNeedMoreData = errors.New("protocol matches, but need more data to complete sniffing")

View File

@@ -1,7 +1,6 @@
package quic
import (
"context"
"crypto"
"crypto/aes"
"crypto/tls"
@@ -13,6 +12,7 @@ import (
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/bytespool"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/protocol"
ptls "github.com/xtls/xray-core/common/protocol/tls"
"golang.org/x/crypto/hkdf"
)
@@ -47,22 +47,17 @@ var (
errNotQuicInitial = errors.New("not initial packet")
)
func SniffQUIC(b []byte) (resultReturn *SniffHeader, errorReturn error) {
// In extremely rare cases, this sniffer may cause slice error
// and we set recover() here to prevent crash.
// TODO: Thoroughly fix this panic
defer func() {
if r := recover(); r != nil {
errors.LogError(context.Background(), "Failed to sniff QUIC: ", r)
resultReturn = nil
errorReturn = common.ErrNoClue
}
}()
func SniffQUIC(b []byte) (*SniffHeader, error) {
if len(b) == 0 {
return nil, common.ErrNoClue
}
// Crypto data separated across packets
cryptoLen := 0
cryptoData := bytespool.Alloc(int32(len(b)))
cryptoData := bytespool.Alloc(32767)
defer bytespool.Free(cryptoData)
cache := buf.New()
defer cache.Release()
// Parse QUIC packets
for len(b) > 0 {
@@ -105,13 +100,15 @@ func SniffQUIC(b []byte) (resultReturn *SniffHeader, errorReturn error) {
return nil, errNotQuic
}
tokenLen, err := quicvarint.Read(buffer)
if err != nil || tokenLen > uint64(len(b)) {
return nil, errNotQuic
}
if isQuicInitial { // Only initial packets have token, see https://datatracker.ietf.org/doc/html/rfc9000#section-17.2.2
tokenLen, err := quicvarint.Read(buffer)
if err != nil || tokenLen > uint64(len(b)) {
return nil, errNotQuic
}
if _, err = buffer.ReadBytes(int32(tokenLen)); err != nil {
return nil, errNotQuic
if _, err = buffer.ReadBytes(int32(tokenLen)); err != nil {
return nil, errNotQuic
}
}
packetLen, err := quicvarint.Read(buffer)
@@ -130,9 +127,6 @@ func SniffQUIC(b []byte) (resultReturn *SniffHeader, errorReturn error) {
continue
}
origPNBytes := make([]byte, 4)
copy(origPNBytes, b[hdrLen:hdrLen+4])
var salt []byte
if versionNumber == version1 {
salt = quicSalt
@@ -147,44 +141,34 @@ func SniffQUIC(b []byte) (resultReturn *SniffHeader, errorReturn error) {
return nil, err
}
cache := buf.New()
defer cache.Release()
cache.Clear()
mask := cache.Extend(int32(block.BlockSize()))
block.Encrypt(mask, b[hdrLen+4:hdrLen+4+16])
b[0] ^= mask[0] & 0xf
for i := range b[hdrLen : hdrLen+4] {
packetNumberLength := int(b[0]&0x3 + 1)
for i := range packetNumberLength {
b[hdrLen+i] ^= mask[i+1]
}
packetNumberLength := b[0]&0x3 + 1
if packetNumberLength != 1 {
return nil, errNotQuicInitial
}
var packetNumber uint32
{
n, err := buffer.ReadByte()
if err != nil {
return nil, err
}
packetNumber = uint32(n)
}
extHdrLen := hdrLen + int(packetNumberLength)
copy(b[extHdrLen:hdrLen+4], origPNBytes[packetNumberLength:])
data := b[extHdrLen : int(packetLen)+hdrLen]
key := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic key", 16)
iv := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic iv", 12)
cipher := AEADAESGCMTLS13(key, iv)
nonce := cache.Extend(int32(cipher.NonceSize()))
binary.BigEndian.PutUint64(nonce[len(nonce)-8:], uint64(packetNumber))
_, err = buffer.Read(nonce[len(nonce)-packetNumberLength:])
if err != nil {
return nil, err
}
extHdrLen := hdrLen + packetNumberLength
data := b[extHdrLen : int(packetLen)+hdrLen]
decrypted, err := cipher.Open(b[extHdrLen:extHdrLen], nonce, data, b[:extHdrLen])
if err != nil {
return nil, err
}
buffer = buf.FromBytes(decrypted)
for i := 0; !buffer.IsEmpty(); i++ {
frameType := byte(0x0) // Default to PADDING frame
for !buffer.IsEmpty() {
frameType, _ := buffer.ReadByte()
for frameType == 0x0 && !buffer.IsEmpty() {
frameType, _ = buffer.ReadByte()
}
@@ -234,13 +218,12 @@ func SniffQUIC(b []byte) (resultReturn *SniffHeader, errorReturn error) {
return nil, io.ErrUnexpectedEOF
}
if cryptoLen < int(offset+length) {
cryptoLen = int(offset + length)
if len(cryptoData) < cryptoLen {
newCryptoData := bytespool.Alloc(int32(cryptoLen))
copy(newCryptoData, cryptoData)
bytespool.Free(cryptoData)
cryptoData = newCryptoData
newCryptoLen := int(offset + length)
if len(cryptoData) < newCryptoLen {
return nil, io.ErrShortBuffer
}
wipeBytes(cryptoData[cryptoLen:newCryptoLen])
cryptoLen = newCryptoLen
}
if _, err := buffer.Read(cryptoData[offset : offset+length]); err != nil { // Field: Crypto Data
return nil, io.ErrUnexpectedEOF
@@ -276,7 +259,14 @@ func SniffQUIC(b []byte) (resultReturn *SniffHeader, errorReturn error) {
}
return &SniffHeader{domain: tlsHdr.Domain()}, nil
}
return nil, common.ErrNoClue
// All payload is parsed as valid QUIC packets, but we need more packets for crypto data to read client hello.
return nil, protocol.ErrProtoNeedMoreData
}
func wipeBytes(b []byte) {
for i := range len(b) {
b[i] = 0x0
}
}
func hkdfExpandLabel(hash crypto.Hash, secret, context []byte, label string, length int) []byte {

File diff suppressed because one or more lines are too long

View File

@@ -3,9 +3,9 @@ package tls
import (
"encoding/binary"
"errors"
"strings"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/protocol"
)
type SniffHeader struct {
@@ -59,9 +59,6 @@ func ReadClientHello(data []byte, h *SniffHeader) error {
}
data = data[1+compressionMethodsLen:]
if len(data) == 0 {
return errNotClientHello
}
if len(data) < 2 {
return errNotClientHello
}
@@ -104,13 +101,21 @@ func ReadClientHello(data []byte, h *SniffHeader) error {
return errNotClientHello
}
if nameType == 0 {
serverName := string(d[:nameLen])
// QUIC separated across packets
// May cause the serverName to be incomplete
b := byte(0)
for _, b = range d[:nameLen] {
if b <= ' ' {
return protocol.ErrProtoNeedMoreData
}
}
// An SNI value may not include a
// trailing dot. See
// https://tools.ietf.org/html/rfc6066#section-3.
if strings.HasSuffix(serverName, ".") {
if b == '.' {
return errNotClientHello
}
serverName := string(d[:nameLen])
h.domain = serverName
return nil
}