Update On Sun Mar 8 19:47:26 CET 2026

This commit is contained in:
github-action[bot]
2026-03-08 19:47:26 +01:00
parent 7f8e87395d
commit 7cc100a4e5
47 changed files with 483 additions and 193 deletions
+1
View File
@@ -1292,3 +1292,4 @@ Update On Wed Mar 4 20:06:13 CET 2026
Update On Thu Mar 5 20:25:40 CET 2026
Update On Fri Mar 6 20:05:10 CET 2026
Update On Sat Mar 7 19:46:34 CET 2026
Update On Sun Mar 8 19:47:17 CET 2026
+1 -1
View File
@@ -6,7 +6,7 @@ require (
github.com/bahlo/generic-list-go v0.2.0
github.com/coreos/go-iptables v0.8.0
github.com/dlclark/regexp2 v1.11.5
github.com/enfein/mieru/v3 v3.28.0
github.com/enfein/mieru/v3 v3.29.0
github.com/gobwas/ws v1.4.0
github.com/gofrs/uuid/v5 v5.4.0
github.com/golang/snappy v1.0.0
+2 -2
View File
@@ -20,8 +20,8 @@ github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZ
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dunglas/httpsfv v1.0.2 h1:iERDp/YAfnojSDJ7PW3dj1AReJz4MrwbECSSE59JWL0=
github.com/dunglas/httpsfv v1.0.2/go.mod h1:zID2mqw9mFsnt7YC3vYQ9/cjq30q41W+1AnDwH8TiMg=
github.com/enfein/mieru/v3 v3.28.0 h1:4OsFPUIjKfQ6ymfyX1Laqz7h+zB8TxuK1m0isnYJ8ww=
github.com/enfein/mieru/v3 v3.28.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
github.com/enfein/mieru/v3 v3.29.0 h1:i5Hwl5spEWg4ydvYW86zWSYVJ2uGTf5sLYQmFXHdulQ=
github.com/enfein/mieru/v3 v3.29.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo=
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
+1 -1
View File
@@ -32,7 +32,7 @@ PROJECT_NAME=$(shell basename "${ROOT}")
# - pkg/version/current.go
#
# Use `tools/bump_version.sh` script to change all those files at one shot.
VERSION="3.28.0"
VERSION="3.29.0"
# With .ONESHELL, each recipe is executed in a single shell instance.
# This allows `cd` to affect subsequent commands in the same recipe.
@@ -1,5 +1,5 @@
Package: mieru
Version: 3.28.0
Version: 3.29.0
Section: net
Priority: optional
Architecture: amd64
@@ -1,5 +1,5 @@
Name: mieru
Version: 3.28.0
Version: 3.29.0
Release: 1%{?dist}
Summary: Mieru proxy client
License: GPLv3+
@@ -1,5 +1,5 @@
Package: mieru
Version: 3.28.0
Version: 3.29.0
Section: net
Priority: optional
Architecture: arm64
@@ -1,5 +1,5 @@
Name: mieru
Version: 3.28.0
Version: 3.29.0
Release: 1%{?dist}
Summary: Mieru proxy client
License: GPLv3+
@@ -1,5 +1,5 @@
Package: mita
Version: 3.28.0
Version: 3.29.0
Section: net
Priority: optional
Architecture: amd64
+1 -1
View File
@@ -1,5 +1,5 @@
Name: mita
Version: 3.28.0
Version: 3.29.0
Release: 1%{?dist}
Summary: Mieru proxy server
License: GPLv3+
@@ -1,5 +1,5 @@
Package: mita
Version: 3.28.0
Version: 3.29.0
Section: net
Priority: optional
Architecture: arm64
+1 -1
View File
@@ -1,5 +1,5 @@
Name: mita
Version: 3.28.0
Version: 3.29.0
Release: 1%{?dist}
Summary: Mieru proxy server
License: GPLv3+
+8 -8
View File
@@ -18,32 +18,32 @@ Or you can manually install and configure proxy server using the steps below.
```sh
# Debian / Ubuntu - X86_64
curl -LSO https://github.com/enfein/mieru/releases/download/v3.28.0/mita_3.28.0_amd64.deb
curl -LSO https://github.com/enfein/mieru/releases/download/v3.29.0/mita_3.29.0_amd64.deb
# Debian / Ubuntu - ARM 64
curl -LSO https://github.com/enfein/mieru/releases/download/v3.28.0/mita_3.28.0_arm64.deb
curl -LSO https://github.com/enfein/mieru/releases/download/v3.29.0/mita_3.29.0_arm64.deb
# RedHat / CentOS / Rocky Linux - X86_64
curl -LSO https://github.com/enfein/mieru/releases/download/v3.28.0/mita-3.28.0-1.x86_64.rpm
curl -LSO https://github.com/enfein/mieru/releases/download/v3.29.0/mita-3.29.0-1.x86_64.rpm
# RedHat / CentOS / Rocky Linux - ARM 64
curl -LSO https://github.com/enfein/mieru/releases/download/v3.28.0/mita-3.28.0-1.aarch64.rpm
curl -LSO https://github.com/enfein/mieru/releases/download/v3.29.0/mita-3.29.0-1.aarch64.rpm
```
## Install mita package
```sh
# Debian / Ubuntu - X86_64
sudo dpkg -i mita_3.28.0_amd64.deb
sudo dpkg -i mita_3.29.0_amd64.deb
# Debian / Ubuntu - ARM 64
sudo dpkg -i mita_3.28.0_arm64.deb
sudo dpkg -i mita_3.29.0_arm64.deb
# RedHat / CentOS / Rocky Linux - X86_64
sudo rpm -Uvh --force mita-3.28.0-1.x86_64.rpm
sudo rpm -Uvh --force mita-3.29.0-1.x86_64.rpm
# RedHat / CentOS / Rocky Linux - ARM 64
sudo rpm -Uvh --force mita-3.28.0-1.aarch64.rpm
sudo rpm -Uvh --force mita-3.29.0-1.aarch64.rpm
```
Those instructions can also be used to upgrade the version of mita software package.
+8 -8
View File
@@ -18,32 +18,32 @@ sudo python3 setup.py --lang=zh
```sh
# Debian / Ubuntu - X86_64
curl -LSO https://github.com/enfein/mieru/releases/download/v3.28.0/mita_3.28.0_amd64.deb
curl -LSO https://github.com/enfein/mieru/releases/download/v3.29.0/mita_3.29.0_amd64.deb
# Debian / Ubuntu - ARM 64
curl -LSO https://github.com/enfein/mieru/releases/download/v3.28.0/mita_3.28.0_arm64.deb
curl -LSO https://github.com/enfein/mieru/releases/download/v3.29.0/mita_3.29.0_arm64.deb
# RedHat / CentOS / Rocky Linux - X86_64
curl -LSO https://github.com/enfein/mieru/releases/download/v3.28.0/mita-3.28.0-1.x86_64.rpm
curl -LSO https://github.com/enfein/mieru/releases/download/v3.29.0/mita-3.29.0-1.x86_64.rpm
# RedHat / CentOS / Rocky Linux - ARM 64
curl -LSO https://github.com/enfein/mieru/releases/download/v3.28.0/mita-3.28.0-1.aarch64.rpm
curl -LSO https://github.com/enfein/mieru/releases/download/v3.29.0/mita-3.29.0-1.aarch64.rpm
```
## 安装 mita 软件包
```sh
# Debian / Ubuntu - X86_64
sudo dpkg -i mita_3.28.0_amd64.deb
sudo dpkg -i mita_3.29.0_amd64.deb
# Debian / Ubuntu - ARM 64
sudo dpkg -i mita_3.28.0_arm64.deb
sudo dpkg -i mita_3.29.0_arm64.deb
# RedHat / CentOS / Rocky Linux - X86_64
sudo rpm -Uvh --force mita-3.28.0-1.x86_64.rpm
sudo rpm -Uvh --force mita-3.29.0-1.x86_64.rpm
# RedHat / CentOS / Rocky Linux - ARM 64
sudo rpm -Uvh --force mita-3.28.0-1.aarch64.rpm
sudo rpm -Uvh --force mita-3.29.0-1.aarch64.rpm
```
上述指令也可以用来升级 mita 软件包的版本。
+22 -25
View File
@@ -73,7 +73,7 @@ const (
maxBackOffDuration = 10 * time.Second
)
type sessionState byte
type sessionState int32
const (
sessionInit sessionState = 0
@@ -106,7 +106,7 @@ type Session struct {
mtu int // Underlay maxinum transmission unit
transportProtocol common.TransportProtocol // transport protocol of underlay connection
remoteAddr net.Addr // specify remote network address, used by UDP
state sessionState // session state
state atomic.Int32 // session state
status statusCode // session status
users map[string]*appctlpb.User // all registered users, only used by server
@@ -150,7 +150,6 @@ type Session struct {
rLock sync.Mutex // serialize application read
wLock sync.Mutex // serialize application write
oLock sync.Mutex // serialize the output sequence
sLock sync.Mutex // serialize the state transition
}
var (
@@ -172,7 +171,6 @@ func NewSession(id uint32, isClient bool, mtu int, users map[string]*appctlpb.Us
id: id,
isClient: isClient,
mtu: mtu,
state: sessionInit,
status: statusOK,
users: users,
trafficPattern: trafficPattern,
@@ -429,7 +427,7 @@ func (s *Session) ToSessionInfo() *appctlpb.SessionInfo {
Id: proto.String(fmt.Sprintf("%d", s.id)),
LocalAddr: proto.String("-"),
RemoteAddr: proto.String("-"),
State: proto.String(s.state.String()),
State: proto.String(sessionState(s.state.Load()).String()),
RecvQ: proto.Uint32(uint32(s.recvQueue.Len())),
RecvBuf: proto.Uint32(uint32(s.recvBuf.Len())),
SendQ: proto.Uint32(uint32(s.sendQueue.Len())),
@@ -468,41 +466,40 @@ func (s *Session) ToSessionInfo() *appctlpb.SessionInfo {
}
func (s *Session) isState(target sessionState) bool {
s.sLock.Lock()
defer s.sLock.Unlock()
return s.state == target
return sessionState(s.state.Load()) == target
}
func (s *Session) isStateBefore(target sessionState, include bool) bool {
s.sLock.Lock()
defer s.sLock.Unlock()
state := sessionState(s.state.Load())
if include {
return s.state <= target
return state <= target
} else {
return s.state < target
return state < target
}
}
func (s *Session) isStateAfter(target sessionState, include bool) bool {
s.sLock.Lock()
defer s.sLock.Unlock()
state := sessionState(s.state.Load())
if include {
return s.state >= target
return state >= target
} else {
return s.state > target
return state > target
}
}
func (s *Session) forwardStateTo(new sessionState) {
s.sLock.Lock()
defer s.sLock.Unlock()
if new < s.state {
panic(fmt.Sprintf("Can't move state back from %v to %v", s.state, new))
func (s *Session) forwardStateTo(next sessionState) {
for {
old := s.state.Load()
if int32(next) <= old {
return
}
if s.state.CompareAndSwap(old, int32(next)) {
if log.IsLevelEnabled(log.TraceLevel) {
log.Tracef("%v %v => %v", s, sessionState(old), next)
}
return
}
}
if log.IsLevelEnabled(log.TraceLevel) && s.state != new {
log.Tracef("%v %v => %v", s, s.state, new)
}
s.state = new
}
func (s *Session) writeChunk(b []byte) (n int, err error) {
+5 -4
View File
@@ -27,6 +27,7 @@ import (
"github.com/enfein/mieru/v3/pkg/appctl/appctlpb"
"github.com/enfein/mieru/v3/pkg/common"
"github.com/enfein/mieru/v3/pkg/metrics"
"github.com/enfein/mieru/v3/pkg/rng"
"github.com/enfein/mieru/v3/pkg/stderror"
)
@@ -37,6 +38,10 @@ const (
sessionCleanInterval = 5 * time.Second
)
var (
readOneSegmentTimeout = time.Duration(60+rng.FixedIntVH(61)) * time.Second
)
// baseUnderlay contains a partial implementation of underlay.
type baseUnderlay struct {
isClient bool
@@ -94,8 +99,6 @@ func (b *baseUnderlay) Close() error {
s := v.(*Session)
s.Close()
s.wg.Wait()
s.conn = nil
s = nil
return true
})
close(b.done)
@@ -170,8 +173,6 @@ func (b *baseUnderlay) RemoveSession(s *Session) error {
b.sessionMap.Delete(s.id)
s.Close()
s.wg.Wait()
s.conn = nil
s = nil
return nil
}
+21 -6
View File
@@ -38,8 +38,6 @@ const (
packetNonHeaderPosition = cipher.DefaultNonceSize + MetadataLength + cipher.DefaultOverhead
idleSessionTimeout = time.Minute
readOneSegmentTimeout = 5 * time.Second
)
var packetReplayCache = replay.NewCache(4*1024*1024, cipher.KeyRefreshInterval*3)
@@ -128,8 +126,10 @@ func (u *PacketUnderlay) Close() error {
log.Debugf("Closing %v", u)
u.sessionCleanTicker.Stop()
// Unblock any pending I/O before closing sessions.
u.conn.SetReadDeadline(time.Now())
u.baseUnderlay.Close()
return u.conn.Close()
return nil
}
func (u *PacketUnderlay) TransportProtocol() common.TransportProtocol {
@@ -177,6 +177,7 @@ func (u *PacketUnderlay) RunEventLoop(ctx context.Context) error {
if u.conn == nil {
return stderror.ErrNullPointer
}
defer u.conn.Close()
for {
select {
@@ -192,7 +193,17 @@ func (u *PacketUnderlay) RunEventLoop(ctx context.Context) error {
}
seg, addr, err := u.readOneSegment()
if err != nil {
// If the underlay is closing, return gracefully.
select {
case <-u.done:
u.cleanSessions()
return nil
default:
}
return fmt.Errorf("readOneSegment() failed: %w", err)
} else if seg == nil && addr == nil {
// Timeout reading the segment, will try again later.
continue
}
if log.IsLevelEnabled(log.TraceLevel) {
log.Tracef("%v received %v from peer %v", u, seg, addr)
@@ -296,8 +307,6 @@ func (u *PacketUnderlay) onCloseSession(seg *segment) error {
}
s := session.(*Session)
s.recvChan <- seg
s.wg.Wait()
u.RemoveSession(s)
return nil
}
@@ -316,7 +325,13 @@ func (u *PacketUnderlay) readOneSegment() (*segment, net.Addr, error) {
n, addr, err := u.conn.ReadFrom(b)
if err != nil {
if stderror.IsTimeout(err) {
continue
// No UDP packet received. Caller will retry.
return nil, nil, nil
}
select {
case <-u.done:
return nil, nil, io.ErrClosedPipe
default:
}
return nil, nil, fmt.Errorf("ReadFrom() failed: %w", err)
}
+31 -11
View File
@@ -125,8 +125,10 @@ func (t *StreamUnderlay) Close() error {
log.Debugf("Closing %v", t)
t.sessionCleanTicker.Stop()
// Unblock any pending I/O before closing sessions.
t.conn.SetDeadline(time.Now())
t.baseUnderlay.Close()
return t.conn.Close()
return nil
}
func (t *StreamUnderlay) Addr() net.Addr {
@@ -181,6 +183,7 @@ func (t *StreamUnderlay) RunEventLoop(ctx context.Context) error {
if t.conn == nil {
return stderror.ErrNullPointer
}
defer t.conn.Close()
for {
select {
@@ -196,6 +199,13 @@ func (t *StreamUnderlay) RunEventLoop(ctx context.Context) error {
}
seg, err := t.readOneSegment()
if err != nil {
// If the underlay is closing, return gracefully.
select {
case <-t.done:
t.cleanSessions()
return nil
default:
}
errType := stderror.GetErrorType(err)
if errType == stderror.NO_ERROR {
panic(fmt.Sprintf("%v error type is NO_ERROR while error is not nil", t))
@@ -207,6 +217,9 @@ func (t *StreamUnderlay) RunEventLoop(ctx context.Context) error {
t.drainAfterError()
}
return fmt.Errorf("readOneSegment() failed: %w", err)
} else if seg == nil {
// Timeout reading the segment, will try again later.
continue
}
if log.IsLevelEnabled(log.TraceLevel) {
log.Tracef("%v received %v", t, seg)
@@ -306,8 +319,6 @@ func (t *StreamUnderlay) onCloseSession(seg *segment) error {
}
s := session.(*Session)
s.recvChan <- seg
s.wg.Wait()
t.RemoveSession(s)
return nil
}
@@ -315,6 +326,9 @@ func (t *StreamUnderlay) readOneSegment() (*segment, error) {
var firstRead bool
var err error
common.SetReadTimeout(t.conn, readOneSegmentTimeout)
defer common.SetReadTimeout(t.conn, 0)
// Read encrypted metadata.
readLen := MetadataLength + cipher.DefaultOverhead
if t.recv == nil {
@@ -323,7 +337,11 @@ func (t *StreamUnderlay) readOneSegment() (*segment, error) {
readLen += cipher.DefaultNonceSize
}
encryptedMeta := make([]byte, readLen)
if _, err := io.ReadFull(t.conn, encryptedMeta); err != nil {
if n, err := io.ReadFull(t.conn, encryptedMeta); err != nil {
if stderror.IsTimeout(err) && n == 0 {
// No TCP data received. Caller will retry.
return nil, nil
}
err = fmt.Errorf("metadata: read %d bytes from StreamUnderlay failed: %w", readLen, err)
return nil, stderror.WrapErrorWithType(err, stderror.NETWORK_ERROR)
}
@@ -695,15 +713,17 @@ func (t *StreamUnderlay) drainAfterError() {
buf := make([]byte, bufSize)
// Determine the number of bytes to read.
// Minimum 2 bytes, maximum bufSize bytes.
minRead := rng.IntRange(2, bufSize-254)
// Minimum 0 bytes, maximum bufSize bytes.
minRead := rng.IntRange(0, bufSize-254)
minRead += rng.FixedIntVH(256)
n, err := io.ReadAtLeast(t.conn, buf, minRead)
if err != nil {
log.Debugf("%v read after stream error failed to complete: %v", t, err)
} else {
log.Debugf("%v read at least %d bytes after stream error", t, n)
if minRead > 0 {
n, err := io.ReadAtLeast(t.conn, buf, minRead)
if err != nil {
log.Debugf("%v read after stream error failed to complete: %v", t, err)
} else {
log.Debugf("%v read at least %d bytes after stream error", t, n)
}
}
}
+1 -1
View File
@@ -16,5 +16,5 @@
package version
const (
AppVersion = "3.28.0"
AppVersion = "3.29.0"
)
+1 -1
View File
@@ -6,7 +6,7 @@ require (
github.com/bahlo/generic-list-go v0.2.0
github.com/coreos/go-iptables v0.8.0
github.com/dlclark/regexp2 v1.11.5
github.com/enfein/mieru/v3 v3.28.0
github.com/enfein/mieru/v3 v3.29.0
github.com/gobwas/ws v1.4.0
github.com/gofrs/uuid/v5 v5.4.0
github.com/golang/snappy v1.0.0
+2 -2
View File
@@ -20,8 +20,8 @@ github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZ
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dunglas/httpsfv v1.0.2 h1:iERDp/YAfnojSDJ7PW3dj1AReJz4MrwbECSSE59JWL0=
github.com/dunglas/httpsfv v1.0.2/go.mod h1:zID2mqw9mFsnt7YC3vYQ9/cjq30q41W+1AnDwH8TiMg=
github.com/enfein/mieru/v3 v3.28.0 h1:4OsFPUIjKfQ6ymfyX1Laqz7h+zB8TxuK1m0isnYJ8ww=
github.com/enfein/mieru/v3 v3.28.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
github.com/enfein/mieru/v3 v3.29.0 h1:i5Hwl5spEWg4ydvYW86zWSYVJ2uGTf5sLYQmFXHdulQ=
github.com/enfein/mieru/v3 v3.29.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo=
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
@@ -486,7 +486,7 @@ o.validate = function(self, value, t)
end
local _tcp_node = s.fields["tcp_node"]:formvalue(t)
if _dns_mode and _tcp_node then
if m:get(_tcp_node, "type"):lower() ~= _dns_mode then
if (m:get(_tcp_node, "type") or ""):lower() ~= _dns_mode then
return nil, translatef("TCP node must be '%s' type to use FakeDNS.", _dns_mode)
end
end
@@ -334,6 +334,9 @@ function gen_outbound(flag, node, tag, proxy_table)
if type(fm.tcp) == "table" then
finalmask.tcp = fm.tcp
end
if type(fm.quicParams) == "table" then
finalmask.quicParams = fm.quicParams
end
end
end
end
@@ -658,6 +661,9 @@ function gen_config_server(node)
if type(fm.tcp) == "table" then
finalmask.tcp = fm.tcp
end
if type(fm.quicParams) == "table" then
finalmask.quicParams = fm.quicParams
end
end
end
end
@@ -367,14 +367,14 @@ local function non_file_check(file_path, header_content)
end
local function GeoToRule(rule_name, rule_type, out_path)
if not api.is_finded("geoview") then
log(rule_name .. "生成失败,缺少 geoview 组件。")
return false;
local bin = api.finded_com("geoview")
if not (bin and api.compare_versions(api.get_app_version("geoview"), ">=", "0.1.10")) then
log("[警告] Geoview 组件缺失或版本过低,规则生成流程已被跳过。")
return false
end
local geosite_path = asset_location .. "geosite.dat"
local geoip_path = asset_location .. "geoip.dat"
local file_path = (rule_type == "domain") and geosite_path or geoip_path
local bin = api.get_app_path("geoview")
local geo_arg
if rule_type == "domain" then
if rule_name == "gfwlist" then
@@ -389,7 +389,13 @@ local function GeoToRule(rule_name, rule_type, out_path)
end
local cmd = string.format(bin .. " -input '%s' %s -lowmem=true -output '%s'", file_path, geo_arg, out_path)
sys.exec(cmd)
return true;
local local_file_size = tonumber(fs.stat(out_path, "size") or 0)
if local_file_size == 0 then
os.remove(out_path)
log(rule_name .. " 生成失败,请确保 Geo 文件正确且包含目标规则。")
return false
end
return true
end
--fetch rule
@@ -407,10 +413,10 @@ local function fetch_rule(rule_name, rule_type, url, exclude_domain, max_retries
end
for k, v in ipairs(url) do
local current_file = "/tmp/" .. rule_name .. "_dl" .. k
local success = false
local current_file = "/tmp/" .. rule_name .. "_dl" .. k
local success = false
if v ~= "geo2rule" then
if v ~= "geo2rule" then
for i = 1, max_attempts do
local http_code, header = curl(v, current_file)
if http_code == 200 and not non_file_check(current_file, header) then
@@ -1578,7 +1578,7 @@ end
local function curl(url, file, ua, mode)
if not url or url == "" then return 404 end
local curl_args = {
"-skL", "-w %{http_code}", "--retry 3", "--connect-timeout 3"
"-skL", "-w %{http_code}", "--retry 3", "--connect-timeout 3", "-H 'Accept-Encoding: identity'"
}
if ua and ua ~= "" and ua ~= "curl" then
ua = (ua == "passwall") and ("passwall/" .. api.get_version()) or ua
@@ -1562,7 +1562,7 @@ end
local function curl(url, file, ua, mode)
if not url or url == "" then return 404 end
local curl_args = {
"-skL", "-w %{http_code}", "--retry 3", "--connect-timeout 3"
"-skL", "-w %{http_code}", "--retry 3", "--connect-timeout 3", "-H 'Accept-Encoding: identity'"
}
if ua and ua ~= "" and ua ~= "curl" then
ua = (ua == "passwall2") and ("passwall2/" .. api.get_version()) or ua
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
env:
ARCH: ${{ matrix.arch }}-${{ matrix.sdk }}
FEEDNAME: packages_ci
PACKAGES: luci-app-fchomo luci-app-homeproxy luci-app-momo luci-app-nikki luci-app-openclash luci-app-passwall luci-app-passwall2 luci-app-ssr-plus luci-app-bypass brook hysteria ipt2socks pdnsd-alt redsocks2 shadow-tls trojan tuic-client xray-plugin v2ray-core v2ray-geodata naiveproxy sing-box
PACKAGES: luci-app-fchomo luci-app-homeproxy luci-app-momo luci-app-nikki luci-app-openclash luci-app-passwall luci-app-passwall2 luci-app-ssr-plus brook hysteria ipt2socks pdnsd-alt redsocks2 shadow-tls trojan tuic-client xray-plugin v2ray-core v2ray-geodata naiveproxy sing-box
NO_REFRESH_CHECK: true
IGNORE_ERRORS: true
+1 -1
View File
@@ -68,7 +68,7 @@ jobs:
env:
ARCH: ${{ matrix.arch }}-${{ matrix.sdk }}
FEEDNAME: packages_ci
PACKAGES: luci-app-fchomo luci-app-homeproxy luci-app-momo luci-app-nikki luci-app-openclash luci-app-passwall luci-app-passwall2 luci-app-ssr-plus luci-app-bypass brook hysteria ipt2socks pdnsd-alt redsocks2 shadow-tls trojan tuic-client xray-plugin v2ray-core v2ray-geodata naiveproxy sing-box
PACKAGES: luci-app-fchomo luci-app-homeproxy luci-app-momo luci-app-nikki luci-app-openclash luci-app-passwall luci-app-passwall2 luci-app-ssr-plus brook hysteria ipt2socks pdnsd-alt redsocks2 shadow-tls trojan tuic-client xray-plugin v2ray-core v2ray-geodata naiveproxy sing-box
NO_REFRESH_CHECK: true
IGNORE_ERRORS: true
+3 -3
View File
@@ -9,9 +9,9 @@ PKG_RELEASE:=1
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://gn.googlesource.com/gn.git
PKG_SOURCE_DATE:=2026-02-25
PKG_SOURCE_VERSION:=1a310e88443018837759c952b113846b0096f65b
PKG_MIRROR_HASH:=26905bb57add5d3c9e0931eaac72510c9fcf5e8837f397594b2f6d6961f7637a
PKG_SOURCE_DATE:=2026-03-05
PKG_SOURCE_VERSION:=d8c2f07d653520568da7cace755a87dad241b72d
PKG_MIRROR_HASH:=68c3f56ed9b8aa31d49c1d7620e62b66ff0496f7921e4c234a2237e676e343c7
PKG_LICENSE:=BSD 3-Clause
PKG_LICENSE_FILES:=LICENSE
+2 -2
View File
@@ -3,7 +3,7 @@
#ifndef OUT_LAST_COMMIT_POSITION_H_
#define OUT_LAST_COMMIT_POSITION_H_
#define LAST_COMMIT_POSITION_NUM 2338
#define LAST_COMMIT_POSITION "2338 (1a310e884430)"
#define LAST_COMMIT_POSITION_NUM 2342
#define LAST_COMMIT_POSITION "2342 (d8c2f07d6535)"
#endif // OUT_LAST_COMMIT_POSITION_H_
@@ -486,7 +486,7 @@ o.validate = function(self, value, t)
end
local _tcp_node = s.fields["tcp_node"]:formvalue(t)
if _dns_mode and _tcp_node then
if m:get(_tcp_node, "type"):lower() ~= _dns_mode then
if (m:get(_tcp_node, "type") or ""):lower() ~= _dns_mode then
return nil, translatef("TCP node must be '%s' type to use FakeDNS.", _dns_mode)
end
end
@@ -334,6 +334,9 @@ function gen_outbound(flag, node, tag, proxy_table)
if type(fm.tcp) == "table" then
finalmask.tcp = fm.tcp
end
if type(fm.quicParams) == "table" then
finalmask.quicParams = fm.quicParams
end
end
end
end
@@ -658,6 +661,9 @@ function gen_config_server(node)
if type(fm.tcp) == "table" then
finalmask.tcp = fm.tcp
end
if type(fm.quicParams) == "table" then
finalmask.quicParams = fm.quicParams
end
end
end
end
@@ -367,14 +367,14 @@ local function non_file_check(file_path, header_content)
end
local function GeoToRule(rule_name, rule_type, out_path)
if not api.is_finded("geoview") then
log(rule_name .. "生成失败,缺少 geoview 组件。")
return false;
local bin = api.finded_com("geoview")
if not (bin and api.compare_versions(api.get_app_version("geoview"), ">=", "0.1.10")) then
log("[警告] Geoview 组件缺失或版本过低,规则生成流程已被跳过。")
return false
end
local geosite_path = asset_location .. "geosite.dat"
local geoip_path = asset_location .. "geoip.dat"
local file_path = (rule_type == "domain") and geosite_path or geoip_path
local bin = api.get_app_path("geoview")
local geo_arg
if rule_type == "domain" then
if rule_name == "gfwlist" then
@@ -389,7 +389,13 @@ local function GeoToRule(rule_name, rule_type, out_path)
end
local cmd = string.format(bin .. " -input '%s' %s -lowmem=true -output '%s'", file_path, geo_arg, out_path)
sys.exec(cmd)
return true;
local local_file_size = tonumber(fs.stat(out_path, "size") or 0)
if local_file_size == 0 then
os.remove(out_path)
log(rule_name .. " 生成失败,请确保 Geo 文件正确且包含目标规则。")
return false
end
return true
end
--fetch rule
@@ -407,10 +413,10 @@ local function fetch_rule(rule_name, rule_type, url, exclude_domain, max_retries
end
for k, v in ipairs(url) do
local current_file = "/tmp/" .. rule_name .. "_dl" .. k
local success = false
local current_file = "/tmp/" .. rule_name .. "_dl" .. k
local success = false
if v ~= "geo2rule" then
if v ~= "geo2rule" then
for i = 1, max_attempts do
local http_code, header = curl(v, current_file)
if http_code == 200 and not non_file_check(current_file, header) then
@@ -1578,7 +1578,7 @@ end
local function curl(url, file, ua, mode)
if not url or url == "" then return 404 end
local curl_args = {
"-skL", "-w %{http_code}", "--retry 3", "--connect-timeout 3"
"-skL", "-w %{http_code}", "--retry 3", "--connect-timeout 3", "-H 'Accept-Encoding: identity'"
}
if ua and ua ~= "" and ua ~= "curl" then
ua = (ua == "passwall") and ("passwall/" .. api.get_version()) or ua
@@ -1562,7 +1562,7 @@ end
local function curl(url, file, ua, mode)
if not url or url == "" then return 404 end
local curl_args = {
"-skL", "-w %{http_code}", "--retry 3", "--connect-timeout 3"
"-skL", "-w %{http_code}", "--retry 3", "--connect-timeout 3", "-H 'Accept-Encoding: identity'"
}
if ua and ua ~= "" and ua ~= "curl" then
ua = (ua == "passwall2") and ("passwall2/" .. api.get_version()) or ua
+2 -2
View File
@@ -21,13 +21,13 @@ define Download/geoip
HASH:=c6c1d1be0d28defef55b153e87cb430f94fb480c8f523bf901c5e4ca18d58a00
endef
GEOSITE_VER:=20260307033015
GEOSITE_VER:=20260308091719
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:=6e432a65a6deefa816555eb2c6bc83138479ead2bc88b01d2a51ccea6a9e7315
HASH:=1d8fbd56dbb678271770306060c994b48f18615fc6ae6135f51b0e511bd388fc
endef
GEOSITE_IRAN_VER:=202603020055
@@ -269,6 +269,7 @@ public class CoreConfigContextBuilder
IndexId = $"inner-{Utils.GetGuid(false)}",
ConfigType = EConfigType.ProxyChain,
CoreType = AppManager.Instance.GetCoreType(node, node.ConfigType),
Remarks = node.Remarks,
};
List<string?> childItems = [prevNode?.IndexId, node.IndexId, nextNode?.IndexId];
var chainExtraItem = chainNode.GetProtocolExtra() with
@@ -154,6 +154,7 @@ public static class ConfigHandler
DownMbps = 100
};
config.ClashUIItem ??= new();
config.ClashUIItem.ConnectionsColumnItem ??= new();
config.SystemProxyItem ??= new();
config.WebDavItem ??= new();
config.CheckUpdateItem ??= new();
@@ -208,6 +208,7 @@ public class ClashUIItem
public int ProxiesAutoDelayTestInterval { get; set; } = 10;
public bool ConnectionsAutoRefresh { get; set; }
public int ConnectionsRefreshInterval { get; set; } = 2;
public List<ColumnItem> ConnectionsColumnItem { get; set; }
}
[Serializable]
@@ -74,23 +74,28 @@
<DataGridTextColumn
Width="300"
Binding="{Binding Host}"
Header="{x:Static resx:ResUI.TbSortingHost}" />
Header="{x:Static resx:ResUI.TbSortingHost}"
Tag="Host" />
<DataGridTextColumn
Width="500"
Binding="{Binding Chain}"
Header="{x:Static resx:ResUI.TbSortingChain}" />
Header="{x:Static resx:ResUI.TbSortingChain}"
Tag="Chain" />
<DataGridTextColumn
Width="80"
Binding="{Binding Network}"
Header="{x:Static resx:ResUI.TbSortingNetwork}" />
Header="{x:Static resx:ResUI.TbSortingNetwork}"
Tag="Network" />
<DataGridTextColumn
Width="160"
Binding="{Binding Type}"
Header="{x:Static resx:ResUI.TbSortingType}" />
Header="{x:Static resx:ResUI.TbSortingType}"
Tag="Type" />
<DataGridTextColumn
Width="100"
Binding="{Binding Elapsed}"
Header="{x:Static resx:ResUI.TbSortingTime}" />
Header="{x:Static resx:ResUI.TbSortingTime}"
Tag="Elapsed" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
@@ -2,9 +2,15 @@ namespace v2rayN.Desktop.Views;
public partial class ClashConnectionsView : ReactiveUserControl<ClashConnectionsViewModel>
{
private static Config _config;
private static readonly string _tag = "ClashConnectionsView";
public ClashConnectionsView()
{
InitializeComponent();
_config = AppManager.Instance.Config;
ViewModel = new ClashConnectionsViewModel(UpdateViewHandler);
btnAutofitColumnWidth.Click += BtnAutofitColumnWidth_Click;
@@ -19,7 +25,15 @@ public partial class ClashConnectionsView : ReactiveUserControl<ClashConnections
this.Bind(ViewModel, vm => vm.HostFilter, v => v.txtHostFilter.Text).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ConnectionCloseAllCmd, v => v.btnConnectionCloseAll).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables);
AppEvents.AppExitRequested
.AsObservable()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(_ => StorageUI())
.DisposeWith(disposables);
});
RestoreUI();
}
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
@@ -51,4 +65,74 @@ public partial class ClashConnectionsView : ReactiveUserControl<ClashConnections
{
ViewModel?.ClashConnectionClose(false);
}
#region UI
private void RestoreUI()
{
try
{
var lvColumnItem = _config.ClashUIItem?.ConnectionsColumnItem?.OrderBy(t => t.Index).ToList();
if (lvColumnItem == null)
{
return;
}
var displayIndex = 0;
foreach (var item in lvColumnItem)
{
foreach (var item2 in lstConnections.Columns)
{
if (item2.Tag == null)
{
continue;
}
if (item2.Tag.Equals(item.Name))
{
if (item.Width < 0)
{
item2.IsVisible = false;
}
else
{
item2.Width = new DataGridLength(item.Width, DataGridLengthUnitType.Pixel);
item2.DisplayIndex = displayIndex++;
}
}
}
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
private void StorageUI()
{
try
{
List<ColumnItem> lvColumnItem = new();
foreach (var item2 in lstConnections.Columns)
{
if (item2.Tag == null)
{
continue;
}
lvColumnItem.Add(new()
{
Name = (string)item2.Tag,
Width = (int)(item2.IsVisible == true ? item2.ActualWidth : -1),
Index = item2.DisplayIndex
});
}
_config.ClashUIItem.ConnectionsColumnItem = lvColumnItem;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
#endregion UI
}
@@ -7,6 +7,7 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
{
private static Config _config;
private Window? _window;
private static readonly string _tag = "ProfilesView";
public ProfilesView()
{
@@ -381,7 +382,7 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
}
catch (Exception ex)
{
Logging.SaveLog("ProfilesView", ex);
Logging.SaveLog(_tag, ex);
}
}
@@ -399,53 +400,67 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
private void RestoreUI()
{
var lvColumnItem = _config.UiItem.MainColumnItem.OrderBy(t => t.Index).ToList();
var displayIndex = 0;
foreach (var item in lvColumnItem)
try
{
var lvColumnItem = _config.UiItem.MainColumnItem.OrderBy(t => t.Index).ToList();
var displayIndex = 0;
foreach (var item in lvColumnItem)
{
foreach (var item2 in lstProfiles.Columns)
{
if (item2.Tag == null)
{
continue;
}
if (item2.Tag.Equals(item.Name))
{
if (item.Width < 0)
{
item2.IsVisible = false;
}
else
{
item2.Width = new DataGridLength(item.Width, DataGridLengthUnitType.Pixel);
item2.DisplayIndex = displayIndex++;
}
if (item.Name.ToLower().StartsWith("to"))
{
item2.IsVisible = _config.GuiItem.EnableStatistics;
}
}
}
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
private void StorageUI()
{
try
{
List<ColumnItem> lvColumnItem = new();
foreach (var item2 in lstProfiles.Columns)
{
if (item2.Tag == null)
{
continue;
}
if (item2.Tag.Equals(item.Name))
lvColumnItem.Add(new()
{
if (item.Width < 0)
{
item2.IsVisible = false;
}
else
{
item2.Width = new DataGridLength(item.Width, DataGridLengthUnitType.Pixel);
item2.DisplayIndex = displayIndex++;
}
if (item.Name.ToLower().StartsWith("to"))
{
item2.IsVisible = _config.GuiItem.EnableStatistics;
}
}
Name = (string)item2.Tag,
Width = (int)(item2.IsVisible == true ? item2.ActualWidth : -1),
Index = item2.DisplayIndex
});
}
_config.UiItem.MainColumnItem = lvColumnItem;
}
}
private void StorageUI()
{
List<ColumnItem> lvColumnItem = new();
foreach (var item2 in lstProfiles.Columns)
catch (Exception ex)
{
if (item2.Tag == null)
{
continue;
}
lvColumnItem.Add(new()
{
Name = (string)item2.Tag,
Width = (int)(item2.IsVisible == true ? item2.ActualWidth : -1),
Index = item2.DisplayIndex
});
Logging.SaveLog(_tag, ex);
}
_config.UiItem.MainColumnItem = lvColumnItem;
}
#endregion UI
@@ -2,6 +2,7 @@
x:Class="v2rayN.Views.ClashConnectionsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:base="clr-namespace:v2rayN.Base"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@@ -78,25 +79,30 @@
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.Columns>
<DataGridTextColumn
<base:MyDGTextColumn
Width="300"
Binding="{Binding Host}"
ExName="Host"
Header="{x:Static resx:ResUI.TbSortingHost}" />
<DataGridTextColumn
<base:MyDGTextColumn
Width="500"
Binding="{Binding Chain}"
ExName="Chain"
Header="{x:Static resx:ResUI.TbSortingChain}" />
<DataGridTextColumn
<base:MyDGTextColumn
Width="80"
Binding="{Binding Network}"
ExName="Network"
Header="{x:Static resx:ResUI.TbSortingNetwork}" />
<DataGridTextColumn
<base:MyDGTextColumn
Width="160"
Binding="{Binding Type}"
ExName="Type"
Header="{x:Static resx:ResUI.TbSortingType}" />
<DataGridTextColumn
<base:MyDGTextColumn
Width="100"
Binding="{Binding Elapsed}"
ExName="Elapsed"
Header="{x:Static resx:ResUI.TbSortingTime}" />
</DataGrid.Columns>
</DataGrid>
@@ -1,4 +1,5 @@
using System.Windows.Controls;
using v2rayN.Base;
namespace v2rayN.Views;
@@ -7,9 +8,14 @@ namespace v2rayN.Views;
/// </summary>
public partial class ClashConnectionsView
{
private static Config _config;
private static readonly string _tag = "ClashConnectionsView";
public ClashConnectionsView()
{
InitializeComponent();
_config = AppManager.Instance.Config;
ViewModel = new ClashConnectionsViewModel(UpdateViewHandler);
btnAutofitColumnWidth.Click += BtnAutofitColumnWidth_Click;
@@ -24,7 +30,15 @@ public partial class ClashConnectionsView
this.Bind(ViewModel, vm => vm.HostFilter, v => v.txtHostFilter.Text).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ConnectionCloseAllCmd, v => v.btnConnectionCloseAll).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables);
AppEvents.AppExitRequested
.AsObservable()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(_ => StorageUI())
.DisposeWith(disposables);
});
RestoreUI();
}
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
@@ -48,7 +62,7 @@ public partial class ClashConnectionsView
}
catch (Exception ex)
{
Logging.SaveLog("ClashConnectionsView", ex);
Logging.SaveLog(_tag, ex);
}
}
@@ -56,4 +70,71 @@ public partial class ClashConnectionsView
{
ViewModel?.ClashConnectionClose(false);
}
#region UI
private void RestoreUI()
{
try
{
var lvColumnItem = _config.ClashUIItem?.ConnectionsColumnItem?.OrderBy(t => t.Index).ToList();
if (lvColumnItem == null)
{
return;
}
var displayIndex = 0;
foreach (var item in lvColumnItem)
{
foreach (var col in lstConnections.Columns.Cast<MyDGTextColumn>())
{
if (col.ExName == item.Name)
{
if (item.Width > 0)
{
col.Width = item.Width;
}
col.DisplayIndex = displayIndex++;
break;
}
}
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
private void StorageUI()
{
try
{
List<ColumnItem> lvColumnItem = new();
foreach (var col in lstConnections.Columns.Cast<MyDGTextColumn>())
{
var name = col.ExName;
if (string.IsNullOrWhiteSpace(name))
{
continue;
}
lvColumnItem.Add(new()
{
Name = name,
Width = (int)col.ActualWidth,
Index = col.DisplayIndex
});
}
_config.ClashUIItem.ConnectionsColumnItem = lvColumnItem;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
#endregion UI
}
+41 -27
View File
@@ -10,6 +10,7 @@ namespace v2rayN.Views;
public partial class ProfilesView
{
private static Config _config;
private static readonly string _tag = "ProfilesView";
public ProfilesView()
{
@@ -339,7 +340,7 @@ public partial class ProfilesView
}
catch (Exception ex)
{
Logging.SaveLog("ProfilesView", ex);
Logging.SaveLog(_tag, ex);
}
}
@@ -357,46 +358,59 @@ public partial class ProfilesView
private void RestoreUI()
{
var lvColumnItem = _config.UiItem.MainColumnItem.OrderBy(t => t.Index).ToList();
var displayIndex = 0;
foreach (var item in lvColumnItem)
try
{
foreach (MyDGTextColumn item2 in lstProfiles.Columns)
var lvColumnItem = _config.UiItem.MainColumnItem.OrderBy(t => t.Index).ToList();
var displayIndex = 0;
foreach (var item in lvColumnItem)
{
if (item2.ExName == item.Name)
foreach (var item2 in lstProfiles.Columns.Cast<MyDGTextColumn>())
{
if (item.Width < 0)
if (item2.ExName == item.Name)
{
item2.Visibility = Visibility.Hidden;
}
else
{
item2.Width = item.Width;
item2.DisplayIndex = displayIndex++;
}
if (item.Name.ToLower().StartsWith("to"))
{
item2.Visibility = _config.GuiItem.EnableStatistics ? Visibility.Visible : Visibility.Hidden;
if (item.Width < 0)
{
item2.Visibility = Visibility.Hidden;
}
else
{
item2.Width = item.Width;
item2.DisplayIndex = displayIndex++;
}
if (item.Name.ToLower().StartsWith("to"))
{
item2.Visibility = _config.GuiItem.EnableStatistics ? Visibility.Visible : Visibility.Hidden;
}
}
}
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
private void StorageUI()
{
List<ColumnItem> lvColumnItem = new();
foreach (var t in lstProfiles.Columns)
try
{
var item2 = (MyDGTextColumn)t;
lvColumnItem.Add(new()
List<ColumnItem> lvColumnItem = new();
foreach (var item2 in lstProfiles.Columns.Cast<MyDGTextColumn>())
{
Name = item2.ExName,
Width = (int)(item2.Visibility == Visibility.Visible ? item2.ActualWidth : -1),
Index = item2.DisplayIndex
});
lvColumnItem.Add(new()
{
Name = item2.ExName,
Width = (int)(item2.Visibility == Visibility.Visible ? item2.ActualWidth : -1),
Index = item2.DisplayIndex
});
}
_config.UiItem.MainColumnItem = lvColumnItem;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
_config.UiItem.MainColumnItem = lvColumnItem;
}
#endregion UI
@@ -405,7 +419,7 @@ public partial class ProfilesView
private Point startPoint = new();
private int startIndex = -1;
private string formatData = "ProfileItemModel";
private readonly string formatData = "ProfileItemModel";
/// <summary>
/// Helper to search up the VisualTree
@@ -3,6 +3,8 @@ package com.v2ray.ang.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import com.v2ray.ang.AppConfig
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.V2RayServiceManager
@@ -16,8 +18,24 @@ class BootReceiver : BroadcastReceiver() {
* @param intent The Intent being received.
*/
override fun onReceive(context: Context?, intent: Intent?) {
if (context == null || intent?.action != Intent.ACTION_BOOT_COMPLETED) return
if (!MmkvManager.decodeStartOnBoot() || MmkvManager.getSelectServer().isNullOrEmpty()) return
Log.i(AppConfig.TAG, "BootReceiver received: ${intent?.action}")
if (context == null || intent?.action != Intent.ACTION_BOOT_COMPLETED) {
Log.w(AppConfig.TAG, "BootReceiver: Invalid context or action")
return
}
if (!MmkvManager.decodeStartOnBoot()) {
Log.i(AppConfig.TAG, "BootReceiver: Auto-start on boot is disabled")
return
}
if (MmkvManager.getSelectServer().isNullOrEmpty()) {
Log.w(AppConfig.TAG, "BootReceiver: No server selected")
return
}
Log.i(AppConfig.TAG, "BootReceiver: Starting V2Ray service")
V2RayServiceManager.startVService(context)
}
}
@@ -1,5 +1,6 @@
package com.v2ray.ang.service
import android.annotation.SuppressLint
import android.app.Service
import android.content.Context
import android.content.Intent
@@ -28,6 +29,7 @@ import com.v2ray.ang.util.MyContextWrapper
import com.v2ray.ang.util.Utils
import java.lang.ref.SoftReference
@SuppressLint("VpnServicePolicy")
class V2RayVpnService : VpnService(), ServiceControl {
private lateinit var mInterface: ParcelFileDescriptor
private var isRunning = false
@@ -103,7 +105,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
}
override fun startService() {
if (mInterface == null) {
if (!::mInterface.isInitialized) {
Log.e(AppConfig.TAG, "Failed to create VPN interface")
return
}
@@ -136,7 +138,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
private fun setupVpnService() {
val prepare = prepare(this)
if (prepare != null) {
Log.e(AppConfig.TAG, "VPN preparation failed")
Log.e(AppConfig.TAG, "VPN preparation failed - VPN permission not granted")
stopSelf()
return
}
@@ -165,9 +167,11 @@ class V2RayVpnService : VpnService(), ServiceControl {
// Close the old interface since the parameters have been changed
try {
mInterface.close()
} catch (ignored: Exception) {
// ignored
if (::mInterface.isInitialized) {
mInterface.close()
}
} catch (e: Exception) {
Log.w(AppConfig.TAG, "Failed to close old interface", e)
}
// Configure platform-specific features
@@ -330,8 +334,8 @@ class V2RayVpnService : VpnService(), ServiceControl {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
try {
connectivity.unregisterNetworkCallback(defaultNetworkCallback)
} catch (ignored: Exception) {
// ignored
} catch (e: Exception) {
Log.w(AppConfig.TAG, "Failed to unregister network callback", e)
}
}
@@ -349,7 +353,9 @@ class V2RayVpnService : VpnService(), ServiceControl {
stopSelf()
try {
mInterface.close()
if (::mInterface.isInitialized) {
mInterface.close()
}
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to close VPN interface", e)
}