Compare commits

...

4 Commits

Author SHA1 Message Date
wwqgtxx
d5a03901d2 fix: race in close grpc transport 2025-05-20 16:15:04 +08:00
wwqgtxx
257fead538 docs: update config.yaml follow 5cf0f18c 2025-05-20 11:08:42 +08:00
wwqgtxx
c489c5260b fix: hysteria2 hop ports init
https://github.com/MetaCubeX/mihomo/issues/2056
2025-05-20 10:56:14 +08:00
wwqgtxx
8f92b1de13 chore: simplify the single root decompression process 2025-05-20 09:48:05 +08:00
5 changed files with 78 additions and 147 deletions

View File

@ -118,7 +118,10 @@ func (u *UIUpdater) downloadUI() error {
tmpDir := C.Path.Resolve("downloadUI.tmp")
defer os.RemoveAll(tmpDir)
extractedFolder, err := extract(data, tmpDir)
os.RemoveAll(tmpDir) // cleanup tmp dir before extract
log.Debugln("extractedFolder: %s", tmpDir)
err = extract(data, tmpDir)
if err != nil {
return fmt.Errorf("can't extract compressed file: %w", err)
}
@ -136,8 +139,8 @@ func (u *UIUpdater) downloadUI() error {
return fmt.Errorf("prepare UI path failed: %w", err)
}
log.Debugln("moveFolder from %s to %s", extractedFolder, u.externalUIPath)
err = moveDir(extractedFolder, u.externalUIPath) // move files from tmp to target
log.Debugln("moveFolder from %s to %s", tmpDir, u.externalUIPath)
err = moveDir(tmpDir, u.externalUIPath) // move files from tmp to target
if err != nil {
return fmt.Errorf("move UI folder failed: %w", err)
}
@ -154,63 +157,19 @@ func (u *UIUpdater) prepareUIPath() error {
return nil
}
func unzip(data []byte, dest string) (string, error) {
func unzip(data []byte, dest string) error {
r, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
if err != nil {
return "", err
return err
}
// check whether or not only exists singleRoot dir
rootDir := ""
isSingleRoot := true
rootItemCount := 0
for _, f := range r.File {
parts := strings.Split(strings.Trim(f.Name, "/"), "/")
if len(parts) == 0 {
continue
}
if len(parts) == 1 {
isDir := strings.HasSuffix(f.Name, "/")
if !isDir {
isSingleRoot = false
break
}
if rootDir == "" {
rootDir = parts[0]
}
rootItemCount++
}
}
if rootItemCount != 1 {
isSingleRoot = false
}
// build the dir of extraction
var extractedFolder string
if isSingleRoot && rootDir != "" {
// if the singleRoot, use it directly
log.Debugln("Match the singleRoot")
extractedFolder = filepath.Join(dest, rootDir)
log.Debugln("extractedFolder: %s", extractedFolder)
} else {
log.Debugln("Match the multiRoot")
extractedFolder = dest
log.Debugln("extractedFolder: %s", extractedFolder)
}
for _, f := range r.File {
var fpath string
if isSingleRoot && rootDir != "" {
fpath = filepath.Join(dest, f.Name)
} else {
fpath = filepath.Join(extractedFolder, f.Name)
}
fpath := filepath.Join(dest, f.Name)
if !inDest(fpath, dest) {
return "", fmt.Errorf("invalid file path: %s", fpath)
return fmt.Errorf("invalid file path: %s", fpath)
}
info := f.FileInfo()
if info.IsDir() {
@ -221,128 +180,77 @@ func unzip(data []byte, dest string) (string, error) {
continue // disallow symlink
}
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
return "", err
return err
}
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return "", err
return err
}
rc, err := f.Open()
if err != nil {
return "", err
return err
}
_, err = io.Copy(outFile, rc)
outFile.Close()
rc.Close()
if err != nil {
return "", err
return err
}
}
return extractedFolder, nil
return nil
}
func untgz(data []byte, dest string) (string, error) {
func untgz(data []byte, dest string) error {
gzr, err := gzip.NewReader(bytes.NewReader(data))
if err != nil {
return "", err
return err
}
defer gzr.Close()
tr := tar.NewReader(gzr)
rootDir := ""
isSingleRoot := true
rootItemCount := 0
for {
header, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return "", err
}
parts := strings.Split(cleanTarPath(header.Name), string(os.PathSeparator))
if len(parts) == 0 {
continue
}
if len(parts) == 1 {
isDir := header.Typeflag == tar.TypeDir
if !isDir {
isSingleRoot = false
break
}
if rootDir == "" {
rootDir = parts[0]
}
rootItemCount++
}
}
if rootItemCount != 1 {
isSingleRoot = false
}
_ = gzr.Reset(bytes.NewReader(data))
tr = tar.NewReader(gzr)
var extractedFolder string
if isSingleRoot && rootDir != "" {
log.Debugln("Match the singleRoot")
extractedFolder = filepath.Join(dest, rootDir)
log.Debugln("extractedFolder: %s", extractedFolder)
} else {
log.Debugln("Match the multiRoot")
extractedFolder = dest
log.Debugln("extractedFolder: %s", extractedFolder)
}
for {
header, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return "", err
return err
}
var fpath string
if isSingleRoot && rootDir != "" {
fpath = filepath.Join(dest, cleanTarPath(header.Name))
} else {
fpath = filepath.Join(extractedFolder, cleanTarPath(header.Name))
}
fpath := filepath.Join(dest, header.Name)
if !inDest(fpath, dest) {
return "", fmt.Errorf("invalid file path: %s", fpath)
return fmt.Errorf("invalid file path: %s", fpath)
}
switch header.Typeflag {
case tar.TypeDir:
if err = os.MkdirAll(fpath, os.FileMode(header.Mode)); err != nil {
return "", err
return err
}
case tar.TypeReg:
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
return "", err
return err
}
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(header.Mode))
if err != nil {
return "", err
return err
}
if _, err := io.Copy(outFile, tr); err != nil {
outFile.Close()
return "", err
return err
}
outFile.Close()
}
}
return extractedFolder, nil
return nil
}
func extract(data []byte, dest string) (string, error) {
func extract(data []byte, dest string) error {
fileType := detectFileType(data)
log.Debugln("compression Type: %s", fileType)
switch fileType {
@ -351,7 +259,7 @@ func extract(data []byte, dest string) (string, error) {
case typeTarGzip:
return untgz(data, dest)
default:
return "", fmt.Errorf("unknown or unsupported file type")
return fmt.Errorf("unknown or unsupported file type")
}
}
@ -393,6 +301,15 @@ func moveDir(src string, dst string) error {
return err
}
if len(dirEntryList) == 1 && dirEntryList[0].IsDir() {
src = filepath.Join(src, dirEntryList[0].Name())
log.Debugln("match the singleRoot: %s", src)
dirEntryList, err = os.ReadDir(src)
if err != nil {
return err
}
}
for _, dirEntry := range dirEntryList {
err = os.Rename(filepath.Join(src, dirEntry.Name()), filepath.Join(dst, dirEntry.Name()))
if err != nil {

View File

@ -645,6 +645,7 @@ proxies: # socks5
reality-opts:
public-key: xxx
short-id: xxx # optional
support-x25519mlkem768: false # 如果服务端支持可手动设置为true
client-fingerprint: chrome # cannot be empty
- name: "vless-reality-grpc"
@ -664,6 +665,7 @@ proxies: # socks5
reality-opts:
public-key: CrrQSjAG_YkHLwvM2M-7XkKJilgL5upBKCp0od0tLhE
short-id: 10f897e26c4b9478
support-x25519mlkem768: false # 如果服务端支持可手动设置为true
- name: "vless-ws"
type: vless

2
go.mod
View File

@ -27,7 +27,7 @@ require (
github.com/metacubex/randv2 v0.2.0
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7
github.com/metacubex/sing-mux v0.3.2
github.com/metacubex/sing-quic v0.0.0-20250517090120-462e75d27336
github.com/metacubex/sing-quic v0.0.0-20250520025433-6e556a6bef7a
github.com/metacubex/sing-shadowsocks v0.2.9
github.com/metacubex/sing-shadowsocks2 v0.2.3
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2

6
go.sum
View File

@ -120,10 +120,8 @@ github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7 h1:m4nSxvw46JEgxM
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing-mux v0.3.2 h1:nJv52pyRivHcaZJKk2JgxpaVvj1GAXG81scSa9N7ncw=
github.com/metacubex/sing-mux v0.3.2/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
github.com/metacubex/sing-quic v0.0.0-20250511034158-b46e0e3e81b2 h1:wfmYgtECbEYo1slMtyo+2kMqscYYDSjU/TVgS3018F4=
github.com/metacubex/sing-quic v0.0.0-20250511034158-b46e0e3e81b2/go.mod h1:P1kd57U6XXmXv9PbwWdznUGT0k9bKgFJXF0fEORbIlk=
github.com/metacubex/sing-quic v0.0.0-20250517090120-462e75d27336 h1:5BgpaFkTzkePwF1A8rmhCqgyOMG79BLsAhFR8W8SiRo=
github.com/metacubex/sing-quic v0.0.0-20250517090120-462e75d27336/go.mod h1:JPTpf7fpnojsSuwRJExhSZSy63pVbp3VM39+zj+sAJM=
github.com/metacubex/sing-quic v0.0.0-20250520025433-6e556a6bef7a h1:Ho73vGiB94LmtK5T+tKVwtCNEi/YiHmPjlqpHSAmAVs=
github.com/metacubex/sing-quic v0.0.0-20250520025433-6e556a6bef7a/go.mod h1:JPTpf7fpnojsSuwRJExhSZSy63pVbp3VM39+zj+sAJM=
github.com/metacubex/sing-shadowsocks v0.2.9 h1:2e++13WNN7EGjGtvrGLUzW1xrCdQbW2gIFpgw5GEw00=
github.com/metacubex/sing-shadowsocks v0.2.9/go.mod h1:CJSEGO4FWQAWe+ZiLZxCweGdjRR60A61SIoVjdjQeBA=
github.com/metacubex/sing-shadowsocks2 v0.2.3 h1:v3rNS/5Ywh0NIZ6VU/NmdERQIN5RePzyxCFeQsU4Cx0=

View File

@ -18,7 +18,6 @@ import (
"sync"
"time"
"github.com/metacubex/mihomo/common/atomic"
"github.com/metacubex/mihomo/common/buf"
"github.com/metacubex/mihomo/common/pool"
"github.com/metacubex/mihomo/component/ech"
@ -42,16 +41,19 @@ type DialFn = func(ctx context.Context, network, addr string) (net.Conn, error)
type Conn struct {
initFn func() (io.ReadCloser, netAddr, error)
writer io.Writer
writer io.Writer // writer must not nil
closer io.Closer
netAddr
reader io.ReadCloser
once sync.Once
closed atomic.Bool
err error
remain int
br *bufio.Reader
initOnce sync.Once
initErr error
reader io.ReadCloser
br *bufio.Reader
remain int
closeMutex sync.Mutex
closed bool
// deadlines
deadline *time.Timer
}
@ -65,7 +67,7 @@ type Config struct {
func (g *Conn) initReader() {
reader, addr, err := g.initFn()
if err != nil {
g.err = err
g.initErr = err
if closer, ok := g.writer.(io.Closer); ok {
closer.Close()
}
@ -73,17 +75,21 @@ func (g *Conn) initReader() {
}
g.netAddr = addr
if !g.closed.Load() {
g.reader = reader
g.br = bufio.NewReader(reader)
} else {
reader.Close()
g.closeMutex.Lock()
defer g.closeMutex.Unlock()
if g.closed { // if g.Close() be called between g.initFn(), direct close the initFn returned reader
_ = reader.Close()
g.initErr = net.ErrClosed
return
}
g.reader = reader
g.br = bufio.NewReader(reader)
}
func (g *Conn) Init() error {
g.once.Do(g.initReader)
return g.err
g.initOnce.Do(g.initReader)
return g.initErr
}
func (g *Conn) Read(b []byte) (n int, err error) {
@ -100,8 +106,6 @@ func (g *Conn) Read(b []byte) (n int, err error) {
n, err = io.ReadFull(g.br, b[:size])
g.remain -= n
return
} else if g.reader == nil {
return 0, net.ErrClosed
}
// 0x00 grpclength(uint32) 0x0A uleb128 payload
@ -147,8 +151,8 @@ func (g *Conn) Write(b []byte) (n int, err error) {
buf.Write(b)
_, err = g.writer.Write(buf.Bytes())
if err == io.ErrClosedPipe && g.err != nil {
err = g.err
if err == io.ErrClosedPipe && g.initErr != nil {
err = g.initErr
}
if flusher, ok := g.writer.(http.Flusher); ok {
@ -170,8 +174,8 @@ func (g *Conn) WriteBuffer(buffer *buf.Buffer) error {
binary.PutUvarint(header[6:], uint64(dataLen))
_, err := g.writer.Write(buffer.Bytes())
if err == io.ErrClosedPipe && g.err != nil {
err = g.err
if err == io.ErrClosedPipe && g.initErr != nil {
err = g.initErr
}
if flusher, ok := g.writer.(http.Flusher); ok {
@ -186,7 +190,17 @@ func (g *Conn) FrontHeadroom() int {
}
func (g *Conn) Close() error {
g.closed.Store(true)
g.initOnce.Do(func() { // if initReader not called, it should not be run anymore
g.initErr = net.ErrClosed
})
g.closeMutex.Lock()
defer g.closeMutex.Unlock()
if g.closed {
return nil
}
g.closed = true
var errorArr []error
if reader := g.reader; reader != nil {