Merge branch 'refs/heads/Alpha' into Meta

This commit is contained in:
Larvan2 2024-04-29 14:22:39 +08:00
commit 81947304bc
53 changed files with 804 additions and 467 deletions

View File

@ -6,8 +6,8 @@ After=network.target NetworkManager.service systemd-networkd.service iwd.service
Type=simple Type=simple
LimitNPROC=500 LimitNPROC=500
LimitNOFILE=1000000 LimitNOFILE=1000000
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE
Restart=always Restart=always
ExecStartPre=/usr/bin/sleep 2s ExecStartPre=/usr/bin/sleep 2s
ExecStart=/usr/bin/mihomo -d /etc/mihomo ExecStart=/usr/bin/mihomo -d /etc/mihomo

View File

@ -34,6 +34,8 @@ jobs:
- { goos: linux, goarch: amd64, goamd64: v1, output: amd64-compatible, test: test } - { goos: linux, goarch: amd64, goamd64: v1, output: amd64-compatible, test: test }
- { goos: linux, goarch: amd64, goamd64: v3, output: amd64 } - { goos: linux, goarch: amd64, goamd64: v3, output: amd64 }
- { goos: linux, goarch: arm64, output: arm64 } - { goos: linux, goarch: arm64, output: arm64 }
- { goos: linux, goarch: arm, goarm: '5', output: armv5 }
- { goos: linux, goarch: arm, goarm: '6', output: armv6 }
- { goos: linux, goarch: arm, goarm: '7', output: armv7 } - { goos: linux, goarch: arm, goarm: '7', output: armv7 }
- { goos: linux, goarch: mips, mips: hardfloat, output: mips-hardfloat } - { goos: linux, goarch: mips, mips: hardfloat, output: mips-hardfloat }
- { goos: linux, goarch: mips, mips: softfloat, output: mips-softfloat } - { goos: linux, goarch: mips, mips: softfloat, output: mips-softfloat }
@ -141,7 +143,7 @@ jobs:
run: | run: |
go test ./... go test ./...
- name: Update UA - name: Update CA
run: | run: |
sudo apt-get install ca-certificates sudo apt-get install ca-certificates
sudo update-ca-certificates sudo update-ca-certificates
@ -175,6 +177,11 @@ jobs:
else else
ARCH=${{matrix.jobs.goarch}} ARCH=${{matrix.jobs.goarch}}
fi fi
PackageVersion=$(curl -s "https://api.github.com/repos/MetaCubeX/mihomo/releases/latest" | grep -o '"tag_name": "[^"]*' | grep -o '[^"]*$' | sed 's/v//g' )
if [ $(git branch | awk -F ' ' '{print $2}') = "Alpha" ]; then
PackageVersion="$(echo "${PackageVersion}" | awk -F '.' '{$NF = $NF + 1; print}' OFS='.')-${VERSION}"
fi
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/bin mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/bin
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/mihomo mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/mihomo
@ -192,7 +199,7 @@ jobs:
cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN/control <<EOF cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN/control <<EOF
Package: mihomo Package: mihomo
Version: 1.18.2-${VERSION} Version: ${PackageVersion}
Section: Section:
Priority: extra Priority: extra
Architecture: ${ARCH} Architecture: ${ARCH}

View File

@ -47,7 +47,7 @@ func WithDstAddr(addr net.Addr) Addition {
func WithSrcAddr(addr net.Addr) Addition { func WithSrcAddr(addr net.Addr) Addition {
return func(metadata *C.Metadata) { return func(metadata *C.Metadata) {
m := C.Metadata{} m := C.Metadata{}
if err := m.SetRemoteAddr(addr);err ==nil{ if err := m.SetRemoteAddr(addr); err == nil {
metadata.SrcIP = m.DstIP metadata.SrcIP = m.DstIP
metadata.SrcPort = m.DstPort metadata.SrcPort = m.DstPort
} }
@ -57,7 +57,7 @@ func WithSrcAddr(addr net.Addr) Addition {
func WithInAddr(addr net.Addr) Addition { func WithInAddr(addr net.Addr) Addition {
return func(metadata *C.Metadata) { return func(metadata *C.Metadata) {
m := C.Metadata{} m := C.Metadata{}
if err := m.SetRemoteAddr(addr);err ==nil{ if err := m.SetRemoteAddr(addr); err == nil {
metadata.InIP = m.DstIP metadata.InIP = m.DstIP
metadata.InPort = m.DstPort metadata.InPort = m.DstPort
} }

View File

@ -14,7 +14,7 @@ func NewHTTP(target socks5.Addr, srcConn net.Conn, conn net.Conn, additions ...A
metadata.Type = C.HTTP metadata.Type = C.HTTP
metadata.RawSrcAddr = srcConn.RemoteAddr() metadata.RawSrcAddr = srcConn.RemoteAddr()
metadata.RawDstAddr = srcConn.LocalAddr() metadata.RawDstAddr = srcConn.LocalAddr()
ApplyAdditions(metadata, WithSrcAddr(srcConn.RemoteAddr()), WithInAddr(conn.LocalAddr())) ApplyAdditions(metadata, WithSrcAddr(srcConn.RemoteAddr()), WithInAddr(srcConn.LocalAddr()))
ApplyAdditions(metadata, additions...) ApplyAdditions(metadata, additions...)
return conn, metadata return conn, metadata
} }

View File

@ -3,18 +3,18 @@ package outbound
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"net/netip" "net/netip"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/loopback"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
) )
type Direct struct { type Direct struct {
*Base *Base
loopBack *loopBackDetector loopBack *loopback.Detector
} }
type DirectOption struct { type DirectOption struct {
@ -24,8 +24,8 @@ type DirectOption struct {
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
if d.loopBack.CheckConn(metadata.SourceAddrPort()) { if err := d.loopBack.CheckConn(metadata); err != nil {
return nil, fmt.Errorf("reject loopback connection to: %s", metadata.RemoteAddress()) return nil, err
} }
opts = append(opts, dialer.WithResolver(resolver.DefaultResolver)) opts = append(opts, dialer.WithResolver(resolver.DefaultResolver))
c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...) c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...)
@ -38,8 +38,8 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
// ListenPacketContext implements C.ProxyAdapter // ListenPacketContext implements C.ProxyAdapter
func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
if d.loopBack.CheckPacketConn(metadata.SourceAddrPort()) { if err := d.loopBack.CheckPacketConn(metadata); err != nil {
return nil, fmt.Errorf("reject loopback connection to: %s", metadata.RemoteAddress()) return nil, err
} }
// net.UDPConn.WriteTo only working with *net.UDPAddr, so we need a net.UDPAddr // net.UDPConn.WriteTo only working with *net.UDPAddr, so we need a net.UDPAddr
if !metadata.Resolved() { if !metadata.Resolved() {
@ -68,7 +68,7 @@ func NewDirectWithOption(option DirectOption) *Direct {
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),
}, },
loopBack: newLoopBackDetector(), loopBack: loopback.NewDetector(),
} }
} }
@ -80,7 +80,7 @@ func NewDirect() *Direct {
udp: true, udp: true,
prefer: C.DualStack, prefer: C.DualStack,
}, },
loopBack: newLoopBackDetector(), loopBack: loopback.NewDetector(),
} }
} }
@ -92,6 +92,6 @@ func NewCompatible() *Direct {
udp: true, udp: true,
prefer: C.DualStack, prefer: C.DualStack,
}, },
loopBack: newLoopBackDetector(), loopBack: loopback.NewDetector(),
} }
} }

View File

@ -1,68 +0,0 @@
package outbound
import (
"net/netip"
"github.com/metacubex/mihomo/common/callback"
C "github.com/metacubex/mihomo/constant"
"github.com/puzpuzpuz/xsync/v3"
)
type loopBackDetector struct {
connMap *xsync.MapOf[netip.AddrPort, struct{}]
packetConnMap *xsync.MapOf[netip.AddrPort, struct{}]
}
func newLoopBackDetector() *loopBackDetector {
return &loopBackDetector{
connMap: xsync.NewMapOf[netip.AddrPort, struct{}](),
packetConnMap: xsync.NewMapOf[netip.AddrPort, struct{}](),
}
}
func (l *loopBackDetector) NewConn(conn C.Conn) C.Conn {
metadata := C.Metadata{}
if metadata.SetRemoteAddr(conn.LocalAddr()) != nil {
return conn
}
connAddr := metadata.AddrPort()
if !connAddr.IsValid() {
return conn
}
l.connMap.Store(connAddr, struct{}{})
return callback.NewCloseCallbackConn(conn, func() {
l.connMap.Delete(connAddr)
})
}
func (l *loopBackDetector) NewPacketConn(conn C.PacketConn) C.PacketConn {
metadata := C.Metadata{}
if metadata.SetRemoteAddr(conn.LocalAddr()) != nil {
return conn
}
connAddr := metadata.AddrPort()
if !connAddr.IsValid() {
return conn
}
l.packetConnMap.Store(connAddr, struct{}{})
return callback.NewCloseCallbackPacketConn(conn, func() {
l.packetConnMap.Delete(connAddr)
})
}
func (l *loopBackDetector) CheckConn(connAddr netip.AddrPort) bool {
if !connAddr.IsValid() {
return false
}
_, ok := l.connMap.Load(connAddr)
return ok
}
func (l *loopBackDetector) CheckPacketConn(connAddr netip.AddrPort) bool {
if !connAddr.IsValid() {
return false
}
_, ok := l.packetConnMap.Load(connAddr)
return ok
}

View File

@ -89,14 +89,14 @@ func (d *dnsPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
return len(p), nil return len(p), nil
} }
ctx, cancel := context.WithTimeout(d.ctx, resolver.DefaultDnsRelayTimeout)
defer cancel()
buf := pool.Get(resolver.SafeDnsPacketSize) buf := pool.Get(resolver.SafeDnsPacketSize)
put := func() { _ = pool.Put(buf) } put := func() { _ = pool.Put(buf) }
copy(buf, p) // avoid p be changed after WriteTo returned copy(buf, p) // avoid p be changed after WriteTo returned
go func() { // don't block the WriteTo function go func() { // don't block the WriteTo function
ctx, cancel := context.WithTimeout(d.ctx, resolver.DefaultDnsRelayTimeout)
defer cancel()
buf, err = resolver.RelayDnsPacket(ctx, buf[:len(p)], buf) buf, err = resolver.RelayDnsPacket(ctx, buf[:len(p)], buf)
if err != nil { if err != nil {
put() put()

View File

@ -38,9 +38,17 @@ type WireGuard struct {
device *device.Device device *device.Device
tunDevice wireguard.Device tunDevice wireguard.Device
dialer proxydialer.SingDialer dialer proxydialer.SingDialer
init func(ctx context.Context) error
resolver *dns.Resolver resolver *dns.Resolver
refP *refProxyAdapter refP *refProxyAdapter
initOk atomic.Bool
initMutex sync.Mutex
initErr error
option WireGuardOption
connectAddr M.Socksaddr
localPrefixes []netip.Prefix
closeCh chan struct{} // for test
} }
type WireGuardOption struct { type WireGuardOption struct {
@ -141,19 +149,6 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
} }
runtime.SetFinalizer(outbound, closeWireGuard) runtime.SetFinalizer(outbound, closeWireGuard)
resolv := func(ctx context.Context, address M.Socksaddr) (netip.AddrPort, error) {
if address.Addr.IsValid() {
return address.AddrPort(), nil
}
udpAddr, err := resolveUDPAddrWithPrefer(ctx, "udp", address.String(), outbound.prefer)
if err != nil {
return netip.AddrPort{}, err
}
// net.ResolveUDPAddr maybe return 4in6 address, so unmap at here
addrPort := udpAddr.AddrPort()
return netip.AddrPortFrom(addrPort.Addr().Unmap(), addrPort.Port()), nil
}
var reserved [3]uint8 var reserved [3]uint8
if len(option.Reserved) > 0 { if len(option.Reserved) > 0 {
if len(option.Reserved) != 3 { if len(option.Reserved) != 3 {
@ -162,29 +157,28 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
copy(reserved[:], option.Reserved) copy(reserved[:], option.Reserved)
} }
var isConnect bool var isConnect bool
var connectAddr M.Socksaddr
if len(option.Peers) < 2 { if len(option.Peers) < 2 {
isConnect = true isConnect = true
if len(option.Peers) == 1 { if len(option.Peers) == 1 {
connectAddr = option.Peers[0].Addr() outbound.connectAddr = option.Peers[0].Addr()
} else { } else {
connectAddr = option.Addr() outbound.connectAddr = option.Addr()
} }
} }
outbound.bind = wireguard.NewClientBind(context.Background(), wgSingErrorHandler{outbound.Name()}, outbound.dialer, isConnect, connectAddr.AddrPort(), reserved) outbound.bind = wireguard.NewClientBind(context.Background(), wgSingErrorHandler{outbound.Name()}, outbound.dialer, isConnect, outbound.connectAddr.AddrPort(), reserved)
localPrefixes, err := option.Prefixes() var err error
outbound.localPrefixes, err = option.Prefixes()
if err != nil { if err != nil {
return nil, err return nil, err
} }
var privateKey string
{ {
bytes, err := base64.StdEncoding.DecodeString(option.PrivateKey) bytes, err := base64.StdEncoding.DecodeString(option.PrivateKey)
if err != nil { if err != nil {
return nil, E.Cause(err, "decode private key") return nil, E.Cause(err, "decode private key")
} }
privateKey = hex.EncodeToString(bytes) option.PrivateKey = hex.EncodeToString(bytes)
} }
if len(option.Peers) > 0 { if len(option.Peers) > 0 {
@ -230,110 +224,16 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
option.PreSharedKey = hex.EncodeToString(bytes) option.PreSharedKey = hex.EncodeToString(bytes)
} }
} }
outbound.option = option
var (
initOk atomic.Bool
initMutex sync.Mutex
initErr error
)
outbound.init = func(ctx context.Context) error {
if initOk.Load() {
return nil
}
initMutex.Lock()
defer initMutex.Unlock()
// double check like sync.Once
if initOk.Load() {
return nil
}
if initErr != nil {
return initErr
}
outbound.bind.ResetReservedForEndpoint()
ipcConf := "private_key=" + privateKey
if len(option.Peers) > 0 {
for i, peer := range option.Peers {
destination, err := resolv(ctx, peer.Addr())
if err != nil {
// !!! do not set initErr here !!!
// let us can retry domain resolve in next time
return E.Cause(err, "resolve endpoint domain for peer ", i)
}
ipcConf += "\npublic_key=" + peer.PublicKey
ipcConf += "\nendpoint=" + destination.String()
if peer.PreSharedKey != "" {
ipcConf += "\npreshared_key=" + peer.PreSharedKey
}
for _, allowedIP := range peer.AllowedIPs {
ipcConf += "\nallowed_ip=" + allowedIP
}
if len(peer.Reserved) > 0 {
copy(reserved[:], option.Reserved)
outbound.bind.SetReservedForEndpoint(destination, reserved)
}
}
} else {
ipcConf += "\npublic_key=" + option.PublicKey
destination, err := resolv(ctx, connectAddr)
if err != nil {
// !!! do not set initErr here !!!
// let us can retry domain resolve in next time
return E.Cause(err, "resolve endpoint domain")
}
outbound.bind.SetConnectAddr(destination)
ipcConf += "\nendpoint=" + destination.String()
if option.PreSharedKey != "" {
ipcConf += "\npreshared_key=" + option.PreSharedKey
}
var has4, has6 bool
for _, address := range localPrefixes {
if address.Addr().Is4() {
has4 = true
} else {
has6 = true
}
}
if has4 {
ipcConf += "\nallowed_ip=0.0.0.0/0"
}
if has6 {
ipcConf += "\nallowed_ip=::/0"
}
}
if option.PersistentKeepalive != 0 {
ipcConf += fmt.Sprintf("\npersistent_keepalive_interval=%d", option.PersistentKeepalive)
}
if debug.Enabled {
log.SingLogger.Trace(fmt.Sprintf("[WG](%s) created wireguard ipc conf: \n %s", option.Name, ipcConf))
}
err = outbound.device.IpcSet(ipcConf)
if err != nil {
initErr = E.Cause(err, "setup wireguard")
return initErr
}
err = outbound.tunDevice.Start()
if err != nil {
initErr = err
return initErr
}
initOk.Store(true)
return nil
}
mtu := option.MTU mtu := option.MTU
if mtu == 0 { if mtu == 0 {
mtu = 1408 mtu = 1408
} }
if len(localPrefixes) == 0 { if len(outbound.localPrefixes) == 0 {
return nil, E.New("missing local address") return nil, E.New("missing local address")
} }
outbound.tunDevice, err = wireguard.NewStackDevice(localPrefixes, uint32(mtu)) outbound.tunDevice, err = wireguard.NewStackDevice(outbound.localPrefixes, uint32(mtu))
if err != nil { if err != nil {
return nil, E.Cause(err, "create WireGuard device") return nil, E.Cause(err, "create WireGuard device")
} }
@ -347,7 +247,7 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
}, option.Workers) }, option.Workers)
var has6 bool var has6 bool
for _, address := range localPrefixes { for _, address := range outbound.localPrefixes {
if !address.Addr().Unmap().Is4() { if !address.Addr().Unmap().Is4() {
has6 = true has6 = true
break break
@ -373,11 +273,117 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
return outbound, nil return outbound, nil
} }
func (w *WireGuard) resolve(ctx context.Context, address M.Socksaddr) (netip.AddrPort, error) {
if address.Addr.IsValid() {
return address.AddrPort(), nil
}
udpAddr, err := resolveUDPAddrWithPrefer(ctx, "udp", address.String(), w.prefer)
if err != nil {
return netip.AddrPort{}, err
}
// net.ResolveUDPAddr maybe return 4in6 address, so unmap at here
addrPort := udpAddr.AddrPort()
return netip.AddrPortFrom(addrPort.Addr().Unmap(), addrPort.Port()), nil
}
func (w *WireGuard) init(ctx context.Context) error {
if w.initOk.Load() {
return nil
}
w.initMutex.Lock()
defer w.initMutex.Unlock()
// double check like sync.Once
if w.initOk.Load() {
return nil
}
if w.initErr != nil {
return w.initErr
}
w.bind.ResetReservedForEndpoint()
ipcConf := "private_key=" + w.option.PrivateKey
if len(w.option.Peers) > 0 {
for i, peer := range w.option.Peers {
destination, err := w.resolve(ctx, peer.Addr())
if err != nil {
// !!! do not set initErr here !!!
// let us can retry domain resolve in next time
return E.Cause(err, "resolve endpoint domain for peer ", i)
}
ipcConf += "\npublic_key=" + peer.PublicKey
ipcConf += "\nendpoint=" + destination.String()
if peer.PreSharedKey != "" {
ipcConf += "\npreshared_key=" + peer.PreSharedKey
}
for _, allowedIP := range peer.AllowedIPs {
ipcConf += "\nallowed_ip=" + allowedIP
}
if len(peer.Reserved) > 0 {
var reserved [3]uint8
copy(reserved[:], w.option.Reserved)
w.bind.SetReservedForEndpoint(destination, reserved)
}
}
} else {
ipcConf += "\npublic_key=" + w.option.PublicKey
destination, err := w.resolve(ctx, w.connectAddr)
if err != nil {
// !!! do not set initErr here !!!
// let us can retry domain resolve in next time
return E.Cause(err, "resolve endpoint domain")
}
w.bind.SetConnectAddr(destination)
ipcConf += "\nendpoint=" + destination.String()
if w.option.PreSharedKey != "" {
ipcConf += "\npreshared_key=" + w.option.PreSharedKey
}
var has4, has6 bool
for _, address := range w.localPrefixes {
if address.Addr().Is4() {
has4 = true
} else {
has6 = true
}
}
if has4 {
ipcConf += "\nallowed_ip=0.0.0.0/0"
}
if has6 {
ipcConf += "\nallowed_ip=::/0"
}
}
if w.option.PersistentKeepalive != 0 {
ipcConf += fmt.Sprintf("\npersistent_keepalive_interval=%d", w.option.PersistentKeepalive)
}
if debug.Enabled {
log.SingLogger.Trace(fmt.Sprintf("[WG](%s) created wireguard ipc conf: \n %s", w.option.Name, ipcConf))
}
err := w.device.IpcSet(ipcConf)
if err != nil {
w.initErr = E.Cause(err, "setup wireguard")
return w.initErr
}
err = w.tunDevice.Start()
if err != nil {
w.initErr = err
return w.initErr
}
w.initOk.Store(true)
return nil
}
func closeWireGuard(w *WireGuard) { func closeWireGuard(w *WireGuard) {
if w.device != nil { if w.device != nil {
w.device.Close() w.device.Close()
} }
_ = common.Close(w.tunDevice) _ = common.Close(w.tunDevice)
if w.closeCh != nil {
close(w.closeCh)
}
} }
func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
@ -416,9 +422,6 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
if err = w.init(ctx); err != nil { if err = w.init(ctx); err != nil {
return nil, err return nil, err
} }
if err != nil {
return nil, err
}
if (!metadata.Resolved() || w.resolver != nil) && metadata.Host != "" { if (!metadata.Resolved() || w.resolver != nil) && metadata.Host != "" {
r := resolver.DefaultResolver r := resolver.DefaultResolver
if w.resolver != nil { if w.resolver != nil {

View File

@ -0,0 +1,44 @@
//go:build with_gvisor
package outbound
import (
"context"
"runtime"
"testing"
"time"
)
func TestWireGuardGC(t *testing.T) {
option := WireGuardOption{}
option.Server = "162.159.192.1"
option.Port = 2408
option.PrivateKey = "iOx7749AdqH3IqluG7+0YbGKd0m1mcEXAfGRzpy9rG8="
option.PublicKey = "bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo="
option.Ip = "172.16.0.2"
option.Ipv6 = "2606:4700:110:8d29:be92:3a6a:f4:c437"
option.Reserved = []uint8{51, 69, 125}
wg, err := NewWireGuard(option)
if err != nil {
t.Error(err)
}
closeCh := make(chan struct{})
wg.closeCh = closeCh
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
err = wg.init(ctx)
if err != nil {
t.Error(err)
}
// must do a small sleep before test GC
// because it maybe deadlocks if w.device.Close call too fast after w.device.Start
time.Sleep(10 * time.Millisecond)
wg = nil
runtime.GC()
select {
case <-closeCh:
return
case <-ctx.Done():
t.Error("timeout not GC")
}
}

View File

@ -88,6 +88,29 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
} }
groupOption.ExpectedStatus = status groupOption.ExpectedStatus = status
if len(groupOption.Use) != 0 {
PDs, err := getProviders(providersMap, groupOption.Use)
if err != nil {
return nil, fmt.Errorf("%s: %w", groupName, err)
}
// if test URL is empty, use the first health check URL of providers
if groupOption.URL == "" {
for _, pd := range PDs {
if pd.HealthCheckURL() != "" {
groupOption.URL = pd.HealthCheckURL()
break
}
}
if groupOption.URL == "" {
groupOption.URL = C.DefaultTestURL
}
} else {
addTestUrlToProviders(PDs, groupOption.URL, expectedStatus, groupOption.Filter, uint(groupOption.Interval))
}
providers = append(providers, PDs...)
}
if len(groupOption.Proxies) != 0 { if len(groupOption.Proxies) != 0 {
ps, err := getProxies(proxyMap, groupOption.Proxies) ps, err := getProxies(proxyMap, groupOption.Proxies)
if err != nil { if err != nil {
@ -98,14 +121,15 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
return nil, fmt.Errorf("%s: %w", groupName, errDuplicateProvider) return nil, fmt.Errorf("%s: %w", groupName, errDuplicateProvider)
} }
// select don't need health check if groupOption.URL == "" {
groupOption.URL = C.DefaultTestURL
}
// select don't need auto health check
if groupOption.Type != "select" && groupOption.Type != "relay" { if groupOption.Type != "select" && groupOption.Type != "relay" {
if groupOption.Interval == 0 { if groupOption.Interval == 0 {
groupOption.Interval = 300 groupOption.Interval = 300
} }
if groupOption.URL == "" {
groupOption.URL = C.DefaultTestURL
}
} }
hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.TestTimeout), uint(groupOption.Interval), groupOption.Lazy, expectedStatus) hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.TestTimeout), uint(groupOption.Interval), groupOption.Lazy, expectedStatus)
@ -115,34 +139,10 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
return nil, fmt.Errorf("%s: %w", groupName, err) return nil, fmt.Errorf("%s: %w", groupName, err)
} }
providers = append(providers, pd) providers = append([]types.ProxyProvider{pd}, providers...)
providersMap[groupName] = pd providersMap[groupName] = pd
} }
if len(groupOption.Use) != 0 {
list, err := getProviders(providersMap, groupOption.Use)
if err != nil {
return nil, fmt.Errorf("%s: %w", groupName, err)
}
if groupOption.URL == "" {
for _, p := range list {
if p.HealthCheckURL() != "" {
groupOption.URL = p.HealthCheckURL()
}
break
}
if groupOption.URL == "" {
groupOption.URL = C.DefaultTestURL
}
}
// different proxy groups use different test URL
addTestUrlToProviders(list, groupOption.URL, expectedStatus, groupOption.Filter, uint(groupOption.Interval))
providers = append(providers, list...)
}
var group C.ProxyAdapter var group C.ProxyAdapter
switch groupOption.Type { switch groupOption.Type {
case "url-test": case "url-test":

View File

@ -44,14 +44,16 @@ type proxyProviderSchema struct {
Type string `provider:"type"` Type string `provider:"type"`
Path string `provider:"path,omitempty"` Path string `provider:"path,omitempty"`
URL string `provider:"url,omitempty"` URL string `provider:"url,omitempty"`
Proxy string `provider:"proxy,omitempty"`
Interval int `provider:"interval,omitempty"` Interval int `provider:"interval,omitempty"`
Filter string `provider:"filter,omitempty"` Filter string `provider:"filter,omitempty"`
ExcludeFilter string `provider:"exclude-filter,omitempty"` ExcludeFilter string `provider:"exclude-filter,omitempty"`
ExcludeType string `provider:"exclude-type,omitempty"` ExcludeType string `provider:"exclude-type,omitempty"`
DialerProxy string `provider:"dialer-proxy,omitempty"` DialerProxy string `provider:"dialer-proxy,omitempty"`
HealthCheck healthCheckSchema `provider:"health-check,omitempty"` HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
Override OverrideSchema `provider:"override,omitempty"` Override OverrideSchema `provider:"override,omitempty"`
Header map[string][]string `provider:"header,omitempty"`
} }
func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvider, error) { func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvider, error) {
@ -86,16 +88,14 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
path := C.Path.Resolve(schema.Path) path := C.Path.Resolve(schema.Path)
vehicle = resource.NewFileVehicle(path) vehicle = resource.NewFileVehicle(path)
case "http": case "http":
path := C.Path.GetPathByHash("proxies", schema.URL)
if schema.Path != "" { if schema.Path != "" {
path := C.Path.Resolve(schema.Path) path = C.Path.Resolve(schema.Path)
if !features.CMFA && !C.Path.IsSafePath(path) { if !features.CMFA && !C.Path.IsSafePath(path) {
return nil, fmt.Errorf("%w: %s", errSubPath, path) return nil, fmt.Errorf("%w: %s", errSubPath, path)
} }
vehicle = resource.NewHTTPVehicle(schema.URL, path)
} else {
path := C.Path.GetPathByHash("proxies", schema.URL)
vehicle = resource.NewHTTPVehicle(schema.URL, path)
} }
vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, schema.Header)
default: default:
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type) return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
} }

View File

@ -124,8 +124,8 @@ func (pp *proxySetProvider) getSubscriptionInfo() {
go func() { go func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
defer cancel() defer cancel()
resp, err := mihomoHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(), resp, err := mihomoHttp.HttpRequestWithProxy(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil) http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, pp.Vehicle().Proxy())
if err != nil { if err != nil {
return return
} }
@ -133,8 +133,8 @@ func (pp *proxySetProvider) getSubscriptionInfo() {
userInfoStr := strings.TrimSpace(resp.Header.Get("subscription-userinfo")) userInfoStr := strings.TrimSpace(resp.Header.Get("subscription-userinfo"))
if userInfoStr == "" { if userInfoStr == "" {
resp2, err := mihomoHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(), resp2, err := mihomoHttp.HttpRequestWithProxy(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil) http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil, pp.Vehicle().Proxy())
if err != nil { if err != nil {
return return
} }

View File

@ -330,7 +330,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
vmess["h2-opts"] = h2Opts vmess["h2-opts"] = h2Opts
case "ws": case "ws", "httpupgrade":
headers := make(map[string]any) headers := make(map[string]any)
wsOpts := make(map[string]any) wsOpts := make(map[string]any)
wsOpts["path"] = []string{"/"} wsOpts["path"] = []string{"/"}
@ -338,7 +338,30 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
headers["Host"] = host.(string) headers["Host"] = host.(string)
} }
if path, ok := values["path"]; ok && path != "" { if path, ok := values["path"]; ok && path != "" {
wsOpts["path"] = path.(string) path := path.(string)
pathURL, err := url.Parse(path)
if err == nil {
query := pathURL.Query()
if earlyData := query.Get("ed"); earlyData != "" {
med, err := strconv.Atoi(earlyData)
if err == nil {
switch network {
case "ws":
wsOpts["max-early-data"] = med
wsOpts["early-data-header-name"] = "Sec-WebSocket-Protocol"
case "httpupgrade":
wsOpts["v2ray-http-upgrade-fast-open"] = true
}
query.Del("ed")
pathURL.RawQuery = query.Encode()
path = pathURL.String()
}
}
if earlyDataHeader := query.Get("eh"); earlyDataHeader != "" {
wsOpts["early-data-header-name"] = earlyDataHeader
}
}
wsOpts["path"] = path
} }
wsOpts["headers"] = headers wsOpts["headers"] = headers
vmess["ws-opts"] = wsOpts vmess["ws-opts"] = wsOpts

View File

@ -100,7 +100,7 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
h2Opts["headers"] = headers h2Opts["headers"] = headers
proxy["h2-opts"] = h2Opts proxy["h2-opts"] = h2Opts
case "ws": case "ws", "httpupgrade":
headers := make(map[string]any) headers := make(map[string]any)
wsOpts := make(map[string]any) wsOpts := make(map[string]any)
headers["User-Agent"] = RandUserAgent() headers["User-Agent"] = RandUserAgent()
@ -113,7 +113,13 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
if err != nil { if err != nil {
return fmt.Errorf("bad WebSocket max early data size: %v", err) return fmt.Errorf("bad WebSocket max early data size: %v", err)
} }
wsOpts["max-early-data"] = med switch network {
case "ws":
wsOpts["max-early-data"] = med
wsOpts["early-data-header-name"] = "Sec-WebSocket-Protocol"
case "httpupgrade":
wsOpts["v2ray-http-upgrade-fast-open"] = true
}
} }
if earlyDataHeader := query.Get("eh"); earlyDataHeader != "" { if earlyDataHeader := query.Get("eh"); earlyDataHeader != "" {
wsOpts["early-data-header-name"] = earlyDataHeader wsOpts["early-data-header-name"] = earlyDataHeader

View File

@ -13,6 +13,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
C "github.com/metacubex/mihomo/constant"
) )
var trustCerts []*x509.Certificate var trustCerts []*x509.Certificate
@ -117,7 +119,7 @@ func GetTLSConfig(tlsConfig *tls.Config, fingerprint string, customCA string, cu
var certificate []byte var certificate []byte
var err error var err error
if len(customCA) > 0 { if len(customCA) > 0 {
certificate, err = os.ReadFile(customCA) certificate, err = os.ReadFile(C.Path.Resolve(customCA))
if err != nil { if err != nil {
return nil, fmt.Errorf("load ca error: %w", err) return nil, fmt.Errorf("load ca error: %w", err)
} }

View File

@ -3,6 +3,7 @@ package dhcp
import ( import (
"context" "context"
"net" "net"
"net/netip"
"runtime" "runtime"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
@ -24,5 +25,5 @@ func ListenDHCPClient(ctx context.Context, ifaceName string) (net.PacketConn, er
options = append(options, dialer.WithFallbackBind(true)) options = append(options, dialer.WithFallbackBind(true))
} }
return dialer.ListenPacket(ctx, "udp4", listenAddr, options...) return dialer.ListenPacket(ctx, "udp4", listenAddr, netip.AddrPortFrom(netip.AddrFrom4([4]byte{255, 255, 255, 255}), 67), options...)
} }

View File

@ -75,7 +75,7 @@ func fallbackBindIfaceToDialer(ifaceName string, dialer *net.Dialer, network str
return nil return nil
} }
func fallbackBindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string) (string, error) { func fallbackBindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string, rAddrPort netip.AddrPort) (string, error) {
_, port, err := net.SplitHostPort(address) _, port, err := net.SplitHostPort(address)
if err != nil { if err != nil {
port = "0" port = "0"

View File

@ -46,7 +46,7 @@ func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.A
return nil return nil
} }
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) { func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string, rAddrPort netip.AddrPort) (string, error) {
ifaceObj, err := iface.ResolveInterface(ifaceName) ifaceObj, err := iface.ResolveInterface(ifaceName)
if err != nil { if err != nil {
return "", err return "", err

View File

@ -35,7 +35,7 @@ func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.A
return nil return nil
} }
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) { func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string, rAddrPort netip.AddrPort) (string, error) {
addControlToListenConfig(lc, bindControl(ifaceName)) addControlToListenConfig(lc, bindControl(ifaceName))
return address, nil return address, nil

View File

@ -11,8 +11,8 @@ func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, des
return fallbackBindIfaceToDialer(ifaceName, dialer, network, destination) return fallbackBindIfaceToDialer(ifaceName, dialer, network, destination)
} }
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, network, address string) (string, error) { func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, network, address string, rAddrPort netip.AddrPort) (string, error) {
return fallbackBindIfaceToListenConfig(ifaceName, lc, network, address) return fallbackBindIfaceToListenConfig(ifaceName, lc, network, address, rAddrPort)
} }
func ParseNetwork(network string, addr netip.Addr) string { func ParseNetwork(network string, addr netip.Addr) string {

View File

@ -36,7 +36,7 @@ func bind6(handle syscall.Handle, ifaceIdx int) error {
return err return err
} }
func bindControl(ifaceIdx int) controlFn { func bindControl(ifaceIdx int, rAddrPort netip.AddrPort) controlFn {
return func(ctx context.Context, network, address string, c syscall.RawConn) (err error) { return func(ctx context.Context, network, address string, c syscall.RawConn) (err error) {
addrPort, err := netip.ParseAddrPort(address) addrPort, err := netip.ParseAddrPort(address)
if err == nil && !addrPort.Addr().IsGlobalUnicast() { if err == nil && !addrPort.Addr().IsGlobalUnicast() {
@ -55,7 +55,7 @@ func bindControl(ifaceIdx int) controlFn {
innerErr = bind4err innerErr = bind4err
case "udp6": case "udp6":
// golang will set network to udp6 when listenUDP on wildcard ip (eg: ":0", "") // golang will set network to udp6 when listenUDP on wildcard ip (eg: ":0", "")
if (!addrPort.Addr().IsValid() || addrPort.Addr().IsUnspecified()) && bind6err != nil { if (!addrPort.Addr().IsValid() || addrPort.Addr().IsUnspecified()) && bind6err != nil && rAddrPort.Addr().Unmap().Is4() {
// try bind ipv6, if failed, ignore. it's a workaround for windows disable interface ipv6 // try bind ipv6, if failed, ignore. it's a workaround for windows disable interface ipv6
if bind4err != nil { if bind4err != nil {
innerErr = fmt.Errorf("%w (%s)", bind6err, bind4err) innerErr = fmt.Errorf("%w (%s)", bind6err, bind4err)
@ -76,23 +76,23 @@ func bindControl(ifaceIdx int) controlFn {
} }
} }
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.Addr) error { func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, destination netip.Addr) error {
ifaceObj, err := iface.ResolveInterface(ifaceName) ifaceObj, err := iface.ResolveInterface(ifaceName)
if err != nil { if err != nil {
return err return err
} }
addControlToDialer(dialer, bindControl(ifaceObj.Index)) addControlToDialer(dialer, bindControl(ifaceObj.Index, netip.AddrPortFrom(destination, 0)))
return nil return nil
} }
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) { func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string, rAddrPort netip.AddrPort) (string, error) {
ifaceObj, err := iface.ResolveInterface(ifaceName) ifaceObj, err := iface.ResolveInterface(ifaceName)
if err != nil { if err != nil {
return "", err return "", err
} }
addControlToListenConfig(lc, bindControl(ifaceObj.Index)) addControlToListenConfig(lc, bindControl(ifaceObj.Index, rAddrPort))
return address, nil return address, nil
} }

View File

@ -78,7 +78,7 @@ func DialContext(ctx context.Context, network, address string, options ...Option
} }
} }
func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) { func ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort, options ...Option) (net.PacketConn, error) {
if features.CMFA && DefaultSocketHook != nil { if features.CMFA && DefaultSocketHook != nil {
return listenPacketHooked(ctx, network, address) return listenPacketHooked(ctx, network, address)
} }
@ -91,7 +91,7 @@ func ListenPacket(ctx context.Context, network, address string, options ...Optio
if cfg.fallbackBind { if cfg.fallbackBind {
bind = fallbackBindIfaceToListenConfig bind = fallbackBindIfaceToListenConfig
} }
addr, err := bind(cfg.interfaceName, lc, network, address) addr, err := bind(cfg.interfaceName, lc, network, address, rAddrPort)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -133,11 +133,9 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
var address string var address string
if IP4PEnable { if IP4PEnable {
NewDestination, NewPort := lookupIP4P(destination.String(), port) destination, port = lookupIP4P(destination, port)
address = net.JoinHostPort(NewDestination, NewPort)
} else {
address = net.JoinHostPort(destination.String(), port)
} }
address = net.JoinHostPort(destination.String(), port)
netDialer := opt.netDialer netDialer := opt.netDialer
switch netDialer.(type) { switch netDialer.(type) {
@ -385,7 +383,7 @@ func (d Dialer) ListenPacket(ctx context.Context, network, address string, rAddr
// avoid "The requested address is not valid in its context." // avoid "The requested address is not valid in its context."
opt = WithInterface("") opt = WithInterface("")
} }
return ListenPacket(ctx, ParseNetwork(network, rAddrPort.Addr()), address, opt) return ListenPacket(ctx, ParseNetwork(network, rAddrPort.Addr()), address, rAddrPort, opt)
} }
func NewDialer(options ...Option) Dialer { func NewDialer(options ...Option) Dialer {
@ -399,13 +397,13 @@ func GetIP4PEnable(enableIP4PConvert bool) {
// kanged from https://github.com/heiher/frp/blob/ip4p/client/ip4p.go // kanged from https://github.com/heiher/frp/blob/ip4p/client/ip4p.go
func lookupIP4P(addr string, port string) (string, string) { func lookupIP4P(addr netip.Addr, port string) (netip.Addr, string) {
ip := net.ParseIP(addr) ip := addr.AsSlice()
if ip[0] == 0x20 && ip[1] == 0x01 && if ip[0] == 0x20 && ip[1] == 0x01 &&
ip[2] == 0x00 && ip[3] == 0x00 { ip[2] == 0x00 && ip[3] == 0x00 {
addr = net.IPv4(ip[12], ip[13], ip[14], ip[15]).String() addr = netip.AddrFrom4([4]byte{ip[12], ip[13], ip[14], ip[15]})
port = strconv.Itoa(int(ip[10])<<8 + int(ip[11])) port = strconv.Itoa(int(ip[10])<<8 + int(ip[11]))
log.Debugln("Convert IP4P address %s to %s", ip, net.JoinHostPort(addr, port)) log.Debugln("Convert IP4P address %s to %s", ip, net.JoinHostPort(addr.String(), port))
return addr, port return addr, port
} }
return addr, port return addr, port

View File

@ -17,7 +17,10 @@ import (
) )
func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader) (*http.Response, error) { func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader) (*http.Response, error) {
UA := C.UA return HttpRequestWithProxy(ctx, url, method, header, body, "")
}
func HttpRequestWithProxy(ctx context.Context, url, method string, header map[string][]string, body io.Reader, specialProxy string) (*http.Response, error) {
method = strings.ToUpper(method) method = strings.ToUpper(method)
urlRes, err := URL.Parse(url) urlRes, err := URL.Parse(url)
if err != nil { if err != nil {
@ -32,7 +35,7 @@ func HttpRequest(ctx context.Context, url, method string, header map[string][]st
} }
if _, ok := header["User-Agent"]; !ok { if _, ok := header["User-Agent"]; !ok {
req.Header.Set("User-Agent", UA) req.Header.Set("User-Agent", C.UA)
} }
if err != nil { if err != nil {
@ -54,7 +57,7 @@ func HttpRequest(ctx context.Context, url, method string, header map[string][]st
TLSHandshakeTimeout: 10 * time.Second, TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second, ExpectContinueTimeout: 1 * time.Second,
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
if conn, err := inner.HandleTcp(address); err == nil { if conn, err := inner.HandleTcp(address, specialProxy); err == nil {
return conn, nil return conn, nil
} else { } else {
d := net.Dialer{} d := net.Dialer{}
@ -66,5 +69,4 @@ func HttpRequest(ctx context.Context, url, method string, header map[string][]st
client := http.Client{Transport: transport} client := http.Client{Transport: transport}
return client.Do(req) return client.Do(req)
} }

View File

@ -23,7 +23,7 @@ var (
var interfaces = singledo.NewSingle[map[string]*Interface](time.Second * 20) var interfaces = singledo.NewSingle[map[string]*Interface](time.Second * 20)
func ResolveInterface(name string) (*Interface, error) { func Interfaces() (map[string]*Interface, error) {
value, err, _ := interfaces.Do(func() (map[string]*Interface, error) { value, err, _ := interfaces.Do(func() (map[string]*Interface, error) {
ifaces, err := net.Interfaces() ifaces, err := net.Interfaces()
if err != nil { if err != nil {
@ -69,11 +69,15 @@ func ResolveInterface(name string) (*Interface, error) {
return r, nil return r, nil
}) })
return value, err
}
func ResolveInterface(name string) (*Interface, error) {
ifaces, err := Interfaces()
if err != nil { if err != nil {
return nil, err return nil, err
} }
ifaces := value
iface, ok := ifaces[name] iface, ok := ifaces[name]
if !ok { if !ok {
return nil, ErrIfaceNotFound return nil, ErrIfaceNotFound
@ -82,6 +86,21 @@ func ResolveInterface(name string) (*Interface, error) {
return iface, nil return iface, nil
} }
func IsLocalIp(ip netip.Addr) (bool, error) {
ifaces, err := Interfaces()
if err != nil {
return false, err
}
for _, iface := range ifaces {
for _, addr := range iface.Addrs {
if addr.Contains(ip) {
return true, nil
}
}
}
return false, nil
}
func FlushCache() { func FlushCache() {
interfaces.Reset() interfaces.Reset()
} }

View File

@ -0,0 +1,89 @@
package loopback
import (
"errors"
"fmt"
"net/netip"
"github.com/metacubex/mihomo/common/callback"
"github.com/metacubex/mihomo/component/iface"
C "github.com/metacubex/mihomo/constant"
"github.com/puzpuzpuz/xsync/v3"
)
var ErrReject = errors.New("reject loopback connection")
type Detector struct {
connMap *xsync.MapOf[netip.AddrPort, struct{}]
packetConnMap *xsync.MapOf[uint16, struct{}]
}
func NewDetector() *Detector {
return &Detector{
connMap: xsync.NewMapOf[netip.AddrPort, struct{}](),
packetConnMap: xsync.NewMapOf[uint16, struct{}](),
}
}
func (l *Detector) NewConn(conn C.Conn) C.Conn {
metadata := C.Metadata{}
if metadata.SetRemoteAddr(conn.LocalAddr()) != nil {
return conn
}
connAddr := metadata.AddrPort()
if !connAddr.IsValid() {
return conn
}
l.connMap.Store(connAddr, struct{}{})
return callback.NewCloseCallbackConn(conn, func() {
l.connMap.Delete(connAddr)
})
}
func (l *Detector) NewPacketConn(conn C.PacketConn) C.PacketConn {
metadata := C.Metadata{}
if metadata.SetRemoteAddr(conn.LocalAddr()) != nil {
return conn
}
connAddr := metadata.AddrPort()
if !connAddr.IsValid() {
return conn
}
port := connAddr.Port()
l.packetConnMap.Store(port, struct{}{})
return callback.NewCloseCallbackPacketConn(conn, func() {
l.packetConnMap.Delete(port)
})
}
func (l *Detector) CheckConn(metadata *C.Metadata) error {
connAddr := metadata.SourceAddrPort()
if !connAddr.IsValid() {
return nil
}
if _, ok := l.connMap.Load(connAddr); ok {
return fmt.Errorf("%w to: %s", ErrReject, metadata.RemoteAddress())
}
return nil
}
func (l *Detector) CheckPacketConn(metadata *C.Metadata) error {
connAddr := metadata.SourceAddrPort()
if !connAddr.IsValid() {
return nil
}
isLocalIp, err := iface.IsLocalIp(connAddr.Addr())
if err != nil {
return err
}
if !isLocalIp && !connAddr.Addr().IsLoopback() {
return nil
}
if _, ok := l.packetConnMap.Load(connAddr.Port()); ok {
return fmt.Errorf("%w to: %s", ErrReject, metadata.RemoteAddress())
}
return nil
}

View File

@ -46,7 +46,7 @@ func RelayDnsConn(ctx context.Context, conn net.Conn, readTimeout time.Duration)
ctx, cancel := context.WithTimeout(ctx, DefaultDnsRelayTimeout) ctx, cancel := context.WithTimeout(ctx, DefaultDnsRelayTimeout)
defer cancel() defer cancel()
inData := buff[:n] inData := buff[:n]
msg, err := RelayDnsPacket(ctx, inData, buff) msg, err := relayDnsPacket(ctx, inData, buff, 0)
if err != nil { if err != nil {
return err return err
} }
@ -69,7 +69,7 @@ func RelayDnsConn(ctx context.Context, conn net.Conn, readTimeout time.Duration)
return nil return nil
} }
func RelayDnsPacket(ctx context.Context, payload []byte, target []byte) ([]byte, error) { func relayDnsPacket(ctx context.Context, payload []byte, target []byte, maxSize int) ([]byte, error) {
msg := &D.Msg{} msg := &D.Msg{}
if err := msg.Unpack(payload); err != nil { if err := msg.Unpack(payload); err != nil {
return nil, err return nil, err
@ -83,6 +83,14 @@ func RelayDnsPacket(ctx context.Context, payload []byte, target []byte) ([]byte,
} }
r.SetRcode(msg, r.Rcode) r.SetRcode(msg, r.Rcode)
if maxSize > 0 {
r.Truncate(maxSize)
}
r.Compress = true r.Compress = true
return r.PackBuffer(target) return r.PackBuffer(target)
} }
// RelayDnsPacket will truncate udp message up to SafeDnsPacketSize
func RelayDnsPacket(ctx context.Context, payload []byte, target []byte) ([]byte, error) {
return relayDnsPacket(ctx, payload, target, SafeDnsPacketSize)
}

View File

@ -0,0 +1,39 @@
package resolver
import "sync"
var blacklist struct {
Map map[string]struct{}
Mutex sync.Mutex
}
func init() {
blacklist.Map = make(map[string]struct{})
}
func AddSystemDnsBlacklist(names ...string) {
blacklist.Mutex.Lock()
defer blacklist.Mutex.Unlock()
for _, name := range names {
blacklist.Map[name] = struct{}{}
}
}
func RemoveSystemDnsBlacklist(names ...string) {
blacklist.Mutex.Lock()
defer blacklist.Mutex.Unlock()
for _, name := range names {
delete(blacklist.Map, name)
}
}
func IsSystemDnsBlacklisted(names ...string) bool {
blacklist.Mutex.Lock()
defer blacklist.Mutex.Unlock()
for _, name := range names {
if _, ok := blacklist.Map[name]; ok {
return true
}
}
return false
}

View File

@ -28,13 +28,19 @@ func (f *FileVehicle) Read() ([]byte, error) {
return os.ReadFile(f.path) return os.ReadFile(f.path)
} }
func (f *FileVehicle) Proxy() string {
return ""
}
func NewFileVehicle(path string) *FileVehicle { func NewFileVehicle(path string) *FileVehicle {
return &FileVehicle{path: path} return &FileVehicle{path: path}
} }
type HTTPVehicle struct { type HTTPVehicle struct {
url string url string
path string path string
proxy string
header http.Header
} }
func (h *HTTPVehicle) Url() string { func (h *HTTPVehicle) Url() string {
@ -49,10 +55,14 @@ func (h *HTTPVehicle) Path() string {
return h.path return h.path
} }
func (h *HTTPVehicle) Proxy() string {
return h.proxy
}
func (h *HTTPVehicle) Read() ([]byte, error) { func (h *HTTPVehicle) Read() ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
defer cancel() defer cancel()
resp, err := mihomoHttp.HttpRequest(ctx, h.url, http.MethodGet, nil, nil) resp, err := mihomoHttp.HttpRequestWithProxy(ctx, h.url, http.MethodGet, h.header, nil, h.proxy)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -67,6 +77,6 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
return buf, nil return buf, nil
} }
func NewHTTPVehicle(url string, path string) *HTTPVehicle { func NewHTTPVehicle(url string, path string, proxy string, header http.Header) *HTTPVehicle {
return &HTTPVehicle{url, path} return &HTTPVehicle{url, path, proxy, header}
} }

View File

@ -91,10 +91,11 @@ type Inbound struct {
// Controller config // Controller config
type Controller struct { type Controller struct {
ExternalController string `json:"-"` ExternalController string `json:"-"`
ExternalControllerTLS string `json:"-"` ExternalControllerTLS string `json:"-"`
ExternalUI string `json:"-"` ExternalControllerUnix string `json:"-"`
Secret string `json:"-"` ExternalUI string `json:"-"`
Secret string `json:"-"`
} }
// NTP config // NTP config
@ -304,6 +305,7 @@ type RawConfig struct {
LogLevel log.LogLevel `yaml:"log-level" json:"log-level"` LogLevel log.LogLevel `yaml:"log-level" json:"log-level"`
IPv6 bool `yaml:"ipv6" json:"ipv6"` IPv6 bool `yaml:"ipv6" json:"ipv6"`
ExternalController string `yaml:"external-controller"` ExternalController string `yaml:"external-controller"`
ExternalControllerUnix string `yaml:"external-controller-unix"`
ExternalControllerTLS string `yaml:"external-controller-tls"` ExternalControllerTLS string `yaml:"external-controller-tls"`
ExternalUI string `yaml:"external-ui"` ExternalUI string `yaml:"external-ui"`
ExternalUIURL string `yaml:"external-ui-url" json:"external-ui-url"` ExternalUIURL string `yaml:"external-ui-url" json:"external-ui-url"`
@ -413,7 +415,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
ProxyGroup: []map[string]any{}, ProxyGroup: []map[string]any{},
TCPConcurrent: false, TCPConcurrent: false,
FindProcessMode: P.FindProcessStrict, FindProcessMode: P.FindProcessStrict,
GlobalUA: "clash.meta", GlobalUA: "clash.meta/" + C.Version,
Tun: RawTun{ Tun: RawTun{
Enable: false, Enable: false,
Device: "", Device: "",
@ -678,10 +680,11 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
InboundMPTCP: cfg.InboundMPTCP, InboundMPTCP: cfg.InboundMPTCP,
}, },
Controller: Controller{ Controller: Controller{
ExternalController: cfg.ExternalController, ExternalController: cfg.ExternalController,
ExternalUI: cfg.ExternalUI, ExternalUI: cfg.ExternalUI,
Secret: cfg.Secret, Secret: cfg.Secret,
ExternalControllerTLS: cfg.ExternalControllerTLS, ExternalControllerUnix: cfg.ExternalControllerUnix,
ExternalControllerTLS: cfg.ExternalControllerTLS,
}, },
UnifiedDelay: cfg.UnifiedDelay, UnifiedDelay: cfg.UnifiedDelay,
Mode: cfg.Mode, Mode: cfg.Mode,

View File

@ -31,6 +31,7 @@ func (v VehicleType) String() string {
type Vehicle interface { type Vehicle interface {
Read() ([]byte, error) Read() ([]byte, error)
Path() string Path() string
Proxy() string
Type() VehicleType Type() VehicleType
} }

View File

@ -8,8 +8,10 @@ const (
DomainRegex DomainRegex
GEOSITE GEOSITE
GEOIP GEOIP
IPCIDR SrcGEOIP
IPASN IPASN
SrcIPASN
IPCIDR
SrcIPCIDR SrcIPCIDR
IPSuffix IPSuffix
SrcIPSuffix SrcIPSuffix
@ -48,10 +50,14 @@ func (rt RuleType) String() string {
return "GeoSite" return "GeoSite"
case GEOIP: case GEOIP:
return "GeoIP" return "GeoIP"
case IPCIDR: case SrcGEOIP:
return "IPCIDR" return "SrcGeoIP"
case IPASN: case IPASN:
return "IPASN" return "IPASN"
case SrcIPASN:
return "SrcIPASN"
case IPCIDR:
return "IPCIDR"
case SrcIPCIDR: case SrcIPCIDR:
return "SrcIPCIDR" return "SrcIPCIDR"
case IPSuffix: case IPSuffix:

View File

@ -8,6 +8,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
D "github.com/miekg/dns" D "github.com/miekg/dns"
@ -39,6 +40,9 @@ func (c *systemClient) getDnsClients() ([]dnsClient, error) {
if nameservers, err = dnsReadConfig(); err == nil { if nameservers, err = dnsReadConfig(); err == nil {
log.Debugln("[DNS] system dns update to %s", nameservers) log.Debugln("[DNS] system dns update to %s", nameservers)
for _, addr := range nameservers { for _, addr := range nameservers {
if resolver.IsSystemDnsBlacklisted(addr) {
continue
}
if _, ok := c.dnsClients[addr]; !ok { if _, ok := c.dnsClients[addr]; !ok {
clients := transform( clients := transform(
[]NameServer{{ []NameServer{{

View File

@ -8,15 +8,15 @@ mixed-port: 10801 # HTTP(S) 和 SOCKS 代理混合端口
allow-lan: true # 允许局域网连接 allow-lan: true # 允许局域网连接
bind-address: "*" # 绑定 IP 地址,仅作用于 allow-lan 为 true'*'表示所有地址 bind-address: "*" # 绑定 IP 地址,仅作用于 allow-lan 为 true'*'表示所有地址
authentication: # http,socks入口的验证用户名,密码 authentication: # http,socks 入口的验证用户名,密码
- "username:password" - "username:password"
skip-auth-prefixes: # 设置跳过验证的IP段 skip-auth-prefixes: # 设置跳过验证的 IP
- 127.0.0.1/8 - 127.0.0.1/8
- ::1/128 - ::1/128
lan-allowed-ips: # 允许连接的 IP 地址段,仅作用于 allow-lan 为 true, 默认值为0.0.0.0/0和::/0 lan-allowed-ips: # 允许连接的 IP 地址段,仅作用于 allow-lan 为 true, 默认值为 0.0.0.0/0 和::/0
- 0.0.0.0/0 - 0.0.0.0/0
- ::/0 - ::/0
lan-disallowed-ips: # 禁止连接的 IP 地址段, 黑名单优先级高于白名单, 默认值为空 lan-disallowed-ips: # 禁止连接的 IP 地址段,黑名单优先级高于白名单,默认值为空
- 192.168.0.3/32 - 192.168.0.3/32
# find-process-mode has 3 values:always, strict, off # find-process-mode has 3 values:always, strict, off
@ -58,6 +58,11 @@ external-controller: 0.0.0.0:9093 # RESTful API 监听地址
external-controller-tls: 0.0.0.0:9443 # RESTful API HTTPS 监听地址,需要配置 tls 部分配置文件 external-controller-tls: 0.0.0.0:9443 # RESTful API HTTPS 监听地址,需要配置 tls 部分配置文件
# secret: "123456" # `Authorization:Bearer ${secret}` # secret: "123456" # `Authorization:Bearer ${secret}`
# RESTful API Unix socket 监听地址( windows版本大于17063也可以使用即大于等于1803/RS4版本即可使用
# !!!注意: 从Unix socket访问api接口不会验证secret 如果开启请自行保证安全问题
# 测试方法: curl -v --unix-socket "mihomo.sock" http://localhost/
external-controller-unix: mihomo.sock
# tcp-concurrent: true # TCP 并发连接所有 IP, 将使用最快握手的 TCP # tcp-concurrent: true # TCP 并发连接所有 IP, 将使用最快握手的 TCP
# 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问 # 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问
@ -109,9 +114,9 @@ tun:
# auto-detect-interface: true # 自动识别出口网卡 # auto-detect-interface: true # 自动识别出口网卡
# auto-route: true # 配置路由表 # auto-route: true # 配置路由表
# mtu: 9000 # 最大传输单元 # mtu: 9000 # 最大传输单元
# gso: false # 启用通用分段卸载, 仅支持 Linux # gso: false # 启用通用分段卸载仅支持 Linux
# gso-max-size: 65536 # 通用分段卸载包的最大大小 # gso-max-size: 65536 # 通用分段卸载包的最大大小
# strict-route: true # 将所有连接路由到tun来防止泄漏但你的设备将无法其他设备被访问 # strict-route: true # 将所有连接路由到 tun 来防止泄漏,但你的设备将无法其他设备被访问
inet4-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由 inet4-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由
- 0.0.0.0/1 - 0.0.0.0/1
- 128.0.0.0/1 - 128.0.0.0/1
@ -119,9 +124,9 @@ tun:
- "::/1" - "::/1"
- "8000::/1" - "8000::/1"
# endpoint-independent-nat: false # 启用独立于端点的 NAT # endpoint-independent-nat: false # 启用独立于端点的 NAT
# include-interface: # 限制被路由的接口。默认不限制, 与 `exclude-interface` 冲突 # include-interface: # 限制被路由的接口。默认不限制与 `exclude-interface` 冲突
# - "lan0" # - "lan0"
# exclude-interface: # 排除路由的接口, 与 `include-interface` 冲突 # exclude-interface: # 排除路由的接口与 `include-interface` 冲突
# - "lan1" # - "lan1"
# include-uid: # UID 规则仅在 Linux 下被支持,并且需要 auto-route # include-uid: # UID 规则仅在 Linux 下被支持,并且需要 auto-route
# - 0 # - 0
@ -143,7 +148,7 @@ tun:
# exclude-package: # 排除被路由的 Android 应用包名 # exclude-package: # 排除被路由的 Android 应用包名
# - com.android.captiveportallogin # - com.android.captiveportallogin
#ebpf配置 #ebpf 配置
ebpf: ebpf:
auto-redir: # redirect 模式,仅支持 TCP auto-redir: # redirect 模式,仅支持 TCP
- eth0 - eth0
@ -200,7 +205,7 @@ tunnels: # one line config
target: target.com target: target.com
proxy: proxy proxy: proxy
# DNS配置 # DNS 配置
dns: dns:
cache-algorithm: arc cache-algorithm: arc
enable: false # 关闭将使用系统 DNS enable: false # 关闭将使用系统 DNS
@ -208,7 +213,7 @@ dns:
listen: 0.0.0.0:53 # 开启 DNS 服务器监听 listen: 0.0.0.0:53 # 开启 DNS 服务器监听
# ipv6: false # false 将返回 AAAA 的空结果 # ipv6: false # false 将返回 AAAA 的空结果
# ipv6-timeout: 300 # 单位ms内部双栈并发时向上游查询 AAAA 时,等待 AAAA 的时间,默认 100ms # ipv6-timeout: 300 # 单位ms内部双栈并发时向上游查询 AAAA 时,等待 AAAA 的时间,默认 100ms
# 用于解析 nameserverfallback 以及其他DNS服务器配置的DNS 服务域名 # 用于解析 nameserverfallback 以及其他 DNS 服务器配置的DNS 服务域名
# 只能使用纯 IP 地址,可使用加密 DNS # 只能使用纯 IP 地址,可使用加密 DNS
default-nameserver: default-nameserver:
- 114.114.114.114 - 114.114.114.114
@ -222,12 +227,12 @@ dns:
# use-hosts: true # 查询 hosts # use-hosts: true # 查询 hosts
# 配置不使用fake-ip的域名 # 配置不使用 fake-ip 的域名
# fake-ip-filter: # fake-ip-filter:
# - '*.lan' # - '*.lan'
# - localhost.ptlogin2.qq.com # - localhost.ptlogin2.qq.com
# DNS主要域名配置 # DNS 主要域名配置
# 支持 UDPTCPDoTDoHDoQ # 支持 UDPTCPDoTDoHDoQ
# 这部分为主要 DNS 配置,影响所有直连,确保使用对大陆解析精准的 DNS # 这部分为主要 DNS 配置,影响所有直连,确保使用对大陆解析精准的 DNS
nameserver: nameserver:
@ -239,7 +244,7 @@ dns:
- https://mozilla.cloudflare-dns.com/dns-query#DNS&h3=true # 指定策略组和使用 HTTP/3 - https://mozilla.cloudflare-dns.com/dns-query#DNS&h3=true # 指定策略组和使用 HTTP/3
- dhcp://en0 # dns from dhcp - dhcp://en0 # dns from dhcp
- quic://dns.adguard.com:784 # DNS over QUIC - quic://dns.adguard.com:784 # DNS over QUIC
# - '8.8.8.8#en0' # 兼容指定DNS出口网卡 # - '8.8.8.8#en0' # 兼容指定 DNS 出口网卡
# 当配置 fallback 时,会查询 nameserver 中返回的 IP 是否为 CN非必要配置 # 当配置 fallback 时,会查询 nameserver 中返回的 IP 是否为 CN非必要配置
# 当不是 CN则使用 fallback 中的 DNS 查询结果 # 当不是 CN则使用 fallback 中的 DNS 查询结果
@ -249,7 +254,6 @@ dns:
# - 'tcp://1.1.1.1#ProxyGroupName' # 指定 DNS 过代理查询ProxyGroupName 为策略组名或节点名,过代理配置优先于配置出口网卡,当找不到策略组或节点名则设置为出口网卡 # - 'tcp://1.1.1.1#ProxyGroupName' # 指定 DNS 过代理查询ProxyGroupName 为策略组名或节点名,过代理配置优先于配置出口网卡,当找不到策略组或节点名则设置为出口网卡
# 专用于节点域名解析的 DNS 服务器,非必要配置项 # 专用于节点域名解析的 DNS 服务器,非必要配置项
# 配置服务器若查询失败将使用 nameserver非并发查询
# proxy-server-nameserver: # proxy-server-nameserver:
# - https://dns.google/dns-query # - https://dns.google/dns-query
# - tls://one.one.one.one # - tls://one.one.one.one
@ -338,7 +342,7 @@ proxies: # socks5
# udp-over-tcp: false # udp-over-tcp: false
# ip-version: ipv4 # 设置节点使用 IP 版本可选dualipv4ipv6ipv4-preferipv6-prefer。默认使用 dual # ip-version: ipv4 # 设置节点使用 IP 版本可选dualipv4ipv6ipv4-preferipv6-prefer。默认使用 dual
# ipv4仅使用 IPv4 ipv6仅使用 IPv6 # ipv4仅使用 IPv4 ipv6仅使用 IPv6
# ipv4-prefer优先使用 IPv4 对于 TCP 会进行双栈解析,并发链接但是优先使用 IPv4 链接, # ipv4-prefer优先使用 IPv4 对于 TCP 会进行双栈解析,并发链接但是优先使用 IPv4 链接
# UDP 则为双栈解析,获取结果中的第一个 IPv4 # UDP 则为双栈解析,获取结果中的第一个 IPv4
# ipv6-prefer 同 ipv4-prefer # ipv6-prefer 同 ipv4-prefer
# 现有协议都支持此参数TCP 效果仅在开启 tcp-concurrent 生效 # 现有协议都支持此参数TCP 效果仅在开启 tcp-concurrent 生效
@ -350,7 +354,7 @@ proxies: # socks5
# max-streams: 0 # Maximum multiplexed streams in a connection before opening a new connection. Conflict with max-connections and min-streams. # max-streams: 0 # Maximum multiplexed streams in a connection before opening a new connection. Conflict with max-connections and min-streams.
# padding: false # Enable padding. Requires sing-box server version 1.3-beta9 or later. # padding: false # Enable padding. Requires sing-box server version 1.3-beta9 or later.
# statistic: false # 控制是否将底层连接显示在面板中,方便打断底层连接 # statistic: false # 控制是否将底层连接显示在面板中,方便打断底层连接
# only-tcp: false # 如果设置为true, smux的设置将不会对udp生效udp连接会直接走底层协议 # only-tcp: false # 如果设置为 true, smux 的设置将不会对 udp 生效udp 连接会直接走底层协议
- name: "ss2" - name: "ss2"
type: ss type: ss
@ -383,6 +387,7 @@ proxies: # socks5
# headers: # headers:
# custom: value # custom: value
# v2ray-http-upgrade: false # v2ray-http-upgrade: false
# v2ray-http-upgrade-fast-open: false
- name: "ss4-shadow-tls" - name: "ss4-shadow-tls"
type: ss type: ss
@ -405,18 +410,18 @@ proxies: # socks5
password: [YOUR_SS_PASSWORD] password: [YOUR_SS_PASSWORD]
client-fingerprint: client-fingerprint:
chrome # One of: chrome, ios, firefox or safari chrome # One of: chrome, ios, firefox or safari
# 可以是chrome, ios, firefox, safari中的一个 # 可以是 chrome, ios, firefox, safari 中的一个
plugin: restls plugin: restls
plugin-opts: plugin-opts:
host: host:
"www.microsoft.com" # Must be a TLS 1.3 server "www.microsoft.com" # Must be a TLS 1.3 server
# 应当是一个TLS 1.3 服务器 # 应当是一个 TLS 1.3 服务器
password: [YOUR_RESTLS_PASSWORD] password: [YOUR_RESTLS_PASSWORD]
version-hint: "tls13" version-hint: "tls13"
# Control your post-handshake traffic through restls-script # Control your post-handshake traffic through restls-script
# Hide proxy behaviors like "tls in tls". # Hide proxy behaviors like "tls in tls".
# see https://github.com/3andne/restls/blob/main/Restls-Script:%20Hide%20Your%20Proxy%20Traffic%20Behavior.md # see https://github.com/3andne/restls/blob/main/Restls-Script:%20Hide%20Your%20Proxy%20Traffic%20Behavior.md
# 用restls剧本来控制握手后的行为隐藏"tls in tls"等特征 # 用 restls 剧本来控制握手后的行为,隐藏"tls in tls"等特征
# 详情https://github.com/3andne/restls/blob/main/Restls-Script:%20%E9%9A%90%E8%97%8F%E4%BD%A0%E7%9A%84%E4%BB%A3%E7%90%86%E8%A1%8C%E4%B8%BA.md # 详情https://github.com/3andne/restls/blob/main/Restls-Script:%20%E9%9A%90%E8%97%8F%E4%BD%A0%E7%9A%84%E4%BB%A3%E7%90%86%E8%A1%8C%E4%B8%BA.md
restls-script: "300?100<1,400~100,350~100,600~100,300~200,300~100" restls-script: "300?100<1,400~100,350~100,600~100,300~200,300~100"
@ -428,18 +433,18 @@ proxies: # socks5
password: [YOUR_SS_PASSWORD] password: [YOUR_SS_PASSWORD]
client-fingerprint: client-fingerprint:
chrome # One of: chrome, ios, firefox or safari chrome # One of: chrome, ios, firefox or safari
# 可以是chrome, ios, firefox, safari中的一个 # 可以是 chrome, ios, firefox, safari 中的一个
plugin: restls plugin: restls
plugin-opts: plugin-opts:
host: host:
"vscode.dev" # Must be a TLS 1.2 server "vscode.dev" # Must be a TLS 1.2 server
# 应当是一个TLS 1.2 服务器 # 应当是一个 TLS 1.2 服务器
password: [YOUR_RESTLS_PASSWORD] password: [YOUR_RESTLS_PASSWORD]
version-hint: "tls12" version-hint: "tls12"
restls-script: "1000?100<1,500~100,350~100,600~100,400~200" restls-script: "1000?100<1,500~100,350~100,600~100,400~200"
# vmess # vmess
# cipher支持 auto/aes-128-gcm/chacha20-poly1305/none # cipher 支持 auto/aes-128-gcm/chacha20-poly1305/none
- name: "vmess" - name: "vmess"
type: vmess type: vmess
server: server server: server
@ -461,6 +466,7 @@ proxies: # socks5
# max-early-data: 2048 # max-early-data: 2048
# early-data-header-name: Sec-WebSocket-Protocol # early-data-header-name: Sec-WebSocket-Protocol
# v2ray-http-upgrade: false # v2ray-http-upgrade: false
# v2ray-http-upgrade-fast-open: false
- name: "vmess-h2" - name: "vmess-h2"
type: vmess type: vmess
@ -589,6 +595,7 @@ proxies: # socks5
headers: headers:
Host: example.com Host: example.com
# v2ray-http-upgrade: false # v2ray-http-upgrade: false
# v2ray-http-upgrade-fast-open: false
# Trojan # Trojan
- name: "trojan" - name: "trojan"
@ -633,6 +640,7 @@ proxies: # socks5
# headers: # headers:
# Host: example.com # Host: example.com
# v2ray-http-upgrade: false # v2ray-http-upgrade: false
# v2ray-http-upgrade-fast-open: false
- name: "trojan-xtls" - name: "trojan-xtls"
type: trojan type: trojan
@ -676,11 +684,11 @@ proxies: # socks5
port: 443 port: 443
# ports: 1000,2000-3000,5000 # port 不可省略 # ports: 1000,2000-3000,5000 # port 不可省略
# hop-interval: 15 # hop-interval: 15
# up和down均不写或为0则使用BBR流控 # up down 均不写或为 0 则使用 BBR 流控
# up: "30 Mbps" # 若不写单位,默认为 Mbps # up: "30 Mbps" # 若不写单位,默认为 Mbps
# down: "200 Mbps" # 若不写单位,默认为 Mbps # down: "200 Mbps" # 若不写单位,默认为 Mbps
password: yourpassword password: yourpassword
# obfs: salamander # 默认为空,如果填写则开启obfs目前仅支持salamander # obfs: salamander # 默认为空,如果填写则开启 obfs目前仅支持 salamander
# obfs-password: yourpassword # obfs-password: yourpassword
# sni: server.com # sni: server.com
# skip-cert-verify: false # skip-cert-verify: false
@ -706,9 +714,9 @@ proxies: # socks5
# reserved: [209,98,59] # reserved: [209,98,59]
# 一个出站代理的标识。当值不为空时,将使用指定的 proxy 发出连接 # 一个出站代理的标识。当值不为空时,将使用指定的 proxy 发出连接
# dialer-proxy: "ss1" # dialer-proxy: "ss1"
# remote-dns-resolve: true # 强制dns远程解析默认值为false # remote-dns-resolve: true # 强制 dns 远程解析,默认值为 false
# dns: [ 1.1.1.1, 8.8.8.8 ] # 仅在remote-dns-resolve为true时生效 # dns: [ 1.1.1.1, 8.8.8.8 ] # 仅在 remote-dns-resolve true 时生效
# 如果peers不为空该段落中的allowed-ips不可为空前面段落的server,port,public-key,pre-shared-key均会被忽略但private-key会被保留且只能在顶层指定 # 如果 peers 不为空,该段落中的 allowed-ips 不可为空;前面段落的 server,port,public-key,pre-shared-key 均会被忽略,但 private-key 会被保留且只能在顶层指定
# peers: # peers:
# - server: 162.159.192.1 # - server: 162.159.192.1
# port: 2480 # port: 2480
@ -722,9 +730,9 @@ proxies: # socks5
server: www.example.com server: www.example.com
port: 10443 port: 10443
type: tuic type: tuic
# tuicV4必须填写token 不可同时填写uuid和password # tuicV4 必须填写 token不可同时填写 uuid password
token: TOKEN token: TOKEN
# tuicV5必须填写uuid和password不可同时填写token # tuicV5 必须填写 uuid password不可同时填写 token
uuid: 00000000-0000-0000-0000-000000000001 uuid: 00000000-0000-0000-0000-000000000001
password: PASSWORD_1 password: PASSWORD_1
# ip: 127.0.0.1 # for overwriting the DNS lookup result of the server address set in option 'server' # ip: 127.0.0.1 # for overwriting the DNS lookup result of the server address set in option 'server'
@ -742,8 +750,8 @@ proxies: # socks5
# max-open-streams: 20 # default 100, too many open streams may hurt performance # max-open-streams: 20 # default 100, too many open streams may hurt performance
# sni: example.com # sni: example.com
# #
# meta和sing-box私有扩展将ss-uot用于udp中继开启此选项后udp-relay-mode将失效 # meta sing-box 私有扩展,将 ss-uot 用于 udp 中继,开启此选项后 udp-relay-mode 将失效
# 警告,与原版tuic不兼容 # 警告,与原版 tuic 不兼容!!!
# udp-over-stream: false # udp-over-stream: false
# udp-over-stream-version: 1 # udp-over-stream-version: 1
@ -776,12 +784,12 @@ proxies: # socks5
password: password password: password
privateKey: path privateKey: path
# dns出站会将请求劫持到内部dns模块,所有请求均在内部处理 # dns 出站会将请求劫持到内部 dns 模块,所有请求均在内部处理
- name: "dns-out" - name: "dns-out"
type: dns type: dns
proxy-groups: proxy-groups:
# 代理链,目前relay可以支持udp的只有vmess/vless/trojan/ss/ssr/tuic # 代理链,目前 relay 可以支持 udp 的只有 vmess/vless/trojan/ss/ssr/tuic
# wireguard目前不支持在relay中使用请使用proxy中的dialer-proxy配置项 # wireguard 目前不支持在 relay 中使用,请使用 proxy 中的 dialer-proxy 配置项
# Traffic: mihomo <-> http <-> vmess <-> ss1 <-> ss2 <-> Internet # Traffic: mihomo <-> http <-> vmess <-> ss1 <-> ss2 <-> Internet
- name: "relay" - name: "relay"
type: relay type: relay
@ -855,10 +863,19 @@ proxy-groups:
# Mihomo 格式的节点或支持 *ray 的分享格式 # Mihomo 格式的节点或支持 *ray 的分享格式
proxy-providers: proxy-providers:
provider1: provider1:
type: http # http 的 path 可空置,默认储存路径为 homedir的proxies文件夹,文件名为url的md5 type: http # http 的 path 可空置,默认储存路径为 homedir 的 proxies 文件夹,文件名为 url 的 md5
url: "url" url: "url"
interval: 3600 interval: 3600
path: ./provider1.yaml # 默认只允许存储在 mihomo 的 Home Dir如果想存储到任意位置添加环境变量 SKIP_SAFE_PATH_CHECK=1 path: ./provider1.yaml # 默认只允许存储在 mihomo 的 Home Dir如果想存储到任意位置添加环境变量 SKIP_SAFE_PATH_CHECK=1
proxy: DIRECT
header:
User-Agent:
- "Clash/v1.18.0"
- "mihomo/1.18.3"
# Accept:
# - 'application/vnd.github.v3.raw'
# Authorization:
# - 'token 1231231'
health-check: health-check:
enable: true enable: true
interval: 600 interval: 600
@ -888,8 +905,9 @@ rule-providers:
behavior: classical # domain ipcidr behavior: classical # domain ipcidr
interval: 259200 interval: 259200
path: /path/to/save/file.yaml # 默认只允许存储在 mihomo 的 Home Dir如果想存储到任意位置添加环境变量 SKIP_SAFE_PATH_CHECK=1 path: /path/to/save/file.yaml # 默认只允许存储在 mihomo 的 Home Dir如果想存储到任意位置添加环境变量 SKIP_SAFE_PATH_CHECK=1
type: http # http 的 path 可空置,默认储存路径为 homedir的rules文件夹,文件名为url的md5 type: http # http 的 path 可空置,默认储存路径为 homedir 的 rules 文件夹,文件名为 url 的 md5
url: "url" url: "url"
proxy: DIRECT
rule2: rule2:
behavior: classical behavior: classical
interval: 259200 interval: 259200
@ -938,7 +956,7 @@ listeners:
port: 10808 port: 10808
#listen: 0.0.0.0 # 默认监听 0.0.0.0 #listen: 0.0.0.0 # 默认监听 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules # rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理 # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理
# udp: false # 默认 true # udp: false # 默认 true
- name: http-in-1 - name: http-in-1
@ -946,14 +964,14 @@ listeners:
port: 10809 port: 10809
listen: 0.0.0.0 listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules # rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时这里的proxy名称必须合法否则会出错) # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
- name: mixed-in-1 - name: mixed-in-1
type: mixed # HTTP(S) 和 SOCKS 代理混合 type: mixed # HTTP(S) 和 SOCKS 代理混合
port: 10810 port: 10810
listen: 0.0.0.0 listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules # rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时这里的proxy名称必须合法否则会出错) # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
# udp: false # 默认 true # udp: false # 默认 true
- name: reidr-in-1 - name: reidr-in-1
@ -961,14 +979,14 @@ listeners:
port: 10811 port: 10811
listen: 0.0.0.0 listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules # rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时这里的proxy名称必须合法否则会出错) # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
- name: tproxy-in-1 - name: tproxy-in-1
type: tproxy type: tproxy
port: 10812 port: 10812
listen: 0.0.0.0 listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules # rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时这里的proxy名称必须合法否则会出错) # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
# udp: false # 默认 true # udp: false # 默认 true
- name: shadowsocks-in-1 - name: shadowsocks-in-1
@ -976,7 +994,7 @@ listeners:
port: 10813 port: 10813
listen: 0.0.0.0 listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules # rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时这里的proxy名称必须合法否则会出错) # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
password: vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg= password: vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg=
cipher: 2022-blake3-aes-256-gcm cipher: 2022-blake3-aes-256-gcm
@ -985,13 +1003,13 @@ listeners:
port: 10814 port: 10814
listen: 0.0.0.0 listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules # rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时这里的proxy名称必须合法否则会出错) # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
users: users:
- username: 1 - username: 1
uuid: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68 uuid: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68
alterId: 1 alterId: 1
# ws-path: "/" # 如果不为空则开启websocket传输层 # ws-path: "/" # 如果不为空则开启 websocket 传输层
# 下面两项如果填写则开启tls需要同时填写 # 下面两项如果填写则开启 tls需要同时填写
# certificate: ./server.crt # certificate: ./server.crt
# private-key: ./server.key # private-key: ./server.key
@ -1000,10 +1018,10 @@ listeners:
port: 10815 port: 10815
listen: 0.0.0.0 listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules # rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时这里的proxy名称必须合法否则会出错) # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
# token: # tuicV4填写可以同时填写users # token: # tuicV4 填写(可以同时填写 users
# - TOKEN # - TOKEN
# users: # tuicV5填写可以同时填写token # users: # tuicV5 填写(可以同时填写 token
# 00000000-0000-0000-0000-000000000000: PASSWORD_0 # 00000000-0000-0000-0000-000000000000: PASSWORD_0
# 00000000-0000-0000-0000-000000000001: PASSWORD_1 # 00000000-0000-0000-0000-000000000001: PASSWORD_1
# certificate: ./server.crt # certificate: ./server.crt
@ -1020,25 +1038,25 @@ listeners:
port: 10816 port: 10816
listen: 0.0.0.0 listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules # rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时这里的proxy名称必须合法否则会出错) # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
network: [tcp, udp] network: [tcp, udp]
target: target.com target: target.com
- name: tun-in-1 - name: tun-in-1
type: tun type: tun
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules # rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时这里的proxy名称必须合法否则会出错) # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
stack: system # gvisor / mixed stack: system # gvisor / mixed
dns-hijack: dns-hijack:
- 0.0.0.0:53 # 需要劫持的 DNS - 0.0.0.0:53 # 需要劫持的 DNS
# auto-detect-interface: false # 自动识别出口网卡 # auto-detect-interface: false # 自动识别出口网卡
# auto-route: false # 配置路由表 # auto-route: false # 配置路由表
# mtu: 9000 # 最大传输单元 # mtu: 9000 # 最大传输单元
inet4-address: # 必须手动设置ipv4地址段 inet4-address: # 必须手动设置 ipv4 地址段
- 198.19.0.1/30 - 198.19.0.1/30
inet6-address: # 必须手动设置ipv6地址段 inet6-address: # 必须手动设置 ipv6 地址段
- "fdfe:dcba:9877::1/126" - "fdfe:dcba:9877::1/126"
# strict-route: true # 将所有连接路由到tun来防止泄漏但你的设备将无法其他设备被访问 # strict-route: true # 将所有连接路由到 tun 来防止泄漏,但你的设备将无法其他设备被访问
# inet4-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由 # inet4-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由
# - 0.0.0.0/1 # - 0.0.0.0/1
# - 128.0.0.0/1 # - 128.0.0.0/1
@ -1066,17 +1084,17 @@ listeners:
# exclude-package: # 排除被路由的 Android 应用包名 # exclude-package: # 排除被路由的 Android 应用包名
# - com.android.captiveportallogin # - com.android.captiveportallogin
# 入口配置与 Listener 等价,传入流量将和 socks,mixed 等入口一样按照 mode 所指定的方式进行匹配处理 # 入口配置与 Listener 等价,传入流量将和 socks,mixed 等入口一样按照 mode 所指定的方式进行匹配处理
# shadowsocks,vmess 入口配置(传入流量将和socks,mixed等入口一样按照mode所指定的方式进行匹配处理 # shadowsocks,vmess 入口配置(传入流量将和 socks,mixed 等入口一样按照 mode 所指定的方式进行匹配处理)
# ss-config: ss://2022-blake3-aes-256-gcm:vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg=@:23456 # ss-config: ss://2022-blake3-aes-256-gcm:vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg=@:23456
# vmess-config: vmess://1:9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68@:12345 # vmess-config: vmess://1:9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68@:12345
# tuic服务器入口传入流量将和socks,mixed等入口一样按照mode所指定的方式进行匹配处理 # tuic 服务器入口(传入流量将和 socks,mixed 等入口一样按照 mode 所指定的方式进行匹配处理)
# tuic-server: # tuic-server:
# enable: true # enable: true
# listen: 127.0.0.1:10443 # listen: 127.0.0.1:10443
# token: # tuicV4填写可以同时填写users # token: # tuicV4 填写(可以同时填写 users
# - TOKEN # - TOKEN
# users: # tuicV5填写可以同时填写token # users: # tuicV5 填写(可以同时填写 token
# 00000000-0000-0000-0000-000000000000: PASSWORD_0 # 00000000-0000-0000-0000-000000000000: PASSWORD_0
# 00000000-0000-0000-0000-000000000001: PASSWORD_1 # 00000000-0000-0000-0000-000000000001: PASSWORD_1
# certificate: ./server.crt # certificate: ./server.crt

36
go.mod
View File

@ -13,48 +13,48 @@ require (
github.com/go-chi/cors v1.2.1 github.com/go-chi/cors v1.2.1
github.com/go-chi/render v1.0.3 github.com/go-chi/render v1.0.3
github.com/gobwas/ws v1.3.2 github.com/gobwas/ws v1.3.2
github.com/gofrs/uuid/v5 v5.0.0 github.com/gofrs/uuid/v5 v5.1.0
github.com/insomniacslk/dhcp v0.0.0-20240227161007-c728f5dd21c8 github.com/insomniacslk/dhcp v0.0.0-20240419123447-f1cffa2c0c49
github.com/klauspost/cpuid/v2 v2.2.7 github.com/klauspost/cpuid/v2 v2.2.7
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
github.com/mdlayher/netlink v1.7.2 github.com/mdlayher/netlink v1.7.2
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
github.com/metacubex/quic-go v0.42.1-0.20240319071510-a251e5c66a5c github.com/metacubex/quic-go v0.42.1-0.20240418003344-f006b5735d98
github.com/metacubex/sing-quic v0.0.0-20240310154810-47bca850fc01 github.com/metacubex/sing-quic v0.0.0-20240418004036-814c531c378d
github.com/metacubex/sing-shadowsocks v0.2.6 github.com/metacubex/sing-shadowsocks v0.2.6
github.com/metacubex/sing-shadowsocks2 v0.2.0 github.com/metacubex/sing-shadowsocks2 v0.2.0
github.com/metacubex/sing-tun v0.2.1-0.20240320004934-5d2b35447bfd github.com/metacubex/sing-tun v0.2.6
github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f
github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63 github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63
github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66
github.com/miekg/dns v1.1.58 github.com/miekg/dns v1.1.59
github.com/mroth/weightedrand/v2 v2.1.0 github.com/mroth/weightedrand/v2 v2.1.0
github.com/openacid/low v0.1.21 github.com/openacid/low v0.1.21
github.com/oschwald/maxminddb-golang v1.12.0 github.com/oschwald/maxminddb-golang v1.12.0
github.com/puzpuzpuz/xsync/v3 v3.1.0 github.com/puzpuzpuz/xsync/v3 v3.1.0
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97
github.com/sagernet/sing v0.3.6 github.com/sagernet/sing v0.3.8
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6
github.com/sagernet/sing-shadowtls v0.1.4 github.com/sagernet/sing-shadowtls v0.1.4
github.com/sagernet/utls v1.5.4 github.com/sagernet/utls v1.5.4
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e
github.com/samber/lo v1.39.0 github.com/samber/lo v1.39.0
github.com/shirou/gopsutil/v3 v3.24.2 github.com/shirou/gopsutil/v3 v3.24.3
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
github.com/wk8/go-ordered-map/v2 v2.1.8 github.com/wk8/go-ordered-map/v2 v2.1.8
github.com/zhangyunhao116/fastrand v0.3.0 github.com/zhangyunhao116/fastrand v0.4.0
go.uber.org/automaxprocs v1.5.3 go.uber.org/automaxprocs v1.5.3
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.21.0 golang.org/x/crypto v0.22.0
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f
golang.org/x/net v0.22.0 golang.org/x/net v0.24.0
golang.org/x/sync v0.6.0 golang.org/x/sync v0.7.0
golang.org/x/sys v0.18.0 golang.org/x/sys v0.19.0
google.golang.org/protobuf v1.33.0 google.golang.org/protobuf v1.33.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
lukechampine.com/blake3 v1.2.1 lukechampine.com/blake3 v1.2.2
) )
require ( require (
@ -104,10 +104,10 @@ require (
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
go.uber.org/mock v0.4.0 // indirect go.uber.org/mock v0.4.0 // indirect
golang.org/x/mod v0.15.0 // indirect golang.org/x/mod v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.18.0 // indirect golang.org/x/tools v0.20.0 // indirect
) )
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20240313064558-c197257f6542 replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20240408015159-aa61c96df764

68
go.sum
View File

@ -62,8 +62,8 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.3.2 h1:zlnbNHxumkRvfPWgfXu8RBwyNR1x8wh9cf5PTOCqs9Q= github.com/gobwas/ws v1.3.2 h1:zlnbNHxumkRvfPWgfXu8RBwyNR1x8wh9cf5PTOCqs9Q=
github.com/gobwas/ws v1.3.2/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/gobwas/ws v1.3.2/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M= github.com/gofrs/uuid/v5 v5.1.0 h1:S5rqVKIigghZTCBKPCw0Y+bXkn26K3TB5mvQq2Ix8dk=
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gofrs/uuid/v5 v5.1.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
@ -80,6 +80,8 @@ github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbg
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/insomniacslk/dhcp v0.0.0-20240227161007-c728f5dd21c8 h1:V3plQrMHRWOB5zMm3yNqvBxDQVW1+/wHBSok5uPdmVs= github.com/insomniacslk/dhcp v0.0.0-20240227161007-c728f5dd21c8 h1:V3plQrMHRWOB5zMm3yNqvBxDQVW1+/wHBSok5uPdmVs=
github.com/insomniacslk/dhcp v0.0.0-20240227161007-c728f5dd21c8/go.mod h1:izxuNQZeFrbx2nK2fAyN5iNUB34Fe9j0nK4PwLzAkKw= github.com/insomniacslk/dhcp v0.0.0-20240227161007-c728f5dd21c8/go.mod h1:izxuNQZeFrbx2nK2fAyN5iNUB34Fe9j0nK4PwLzAkKw=
github.com/insomniacslk/dhcp v0.0.0-20240419123447-f1cffa2c0c49 h1:/OuvSMGT9+xnyZ+7MZQ1zdngaCCAdPoSw8B/uurZ7pg=
github.com/insomniacslk/dhcp v0.0.0-20240419123447-f1cffa2c0c49/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
@ -104,26 +106,26 @@ github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvO
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec h1:HxreOiFTUrJXJautEo8rnE1uKTVGY8wtZepY1Tii/Nc= github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec h1:HxreOiFTUrJXJautEo8rnE1uKTVGY8wtZepY1Tii/Nc=
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec/go.mod h1:8BVmQ+3cxjqzWElafm24rb2Ae4jRI6vAXNXWqWjfrXw= github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec/go.mod h1:8BVmQ+3cxjqzWElafm24rb2Ae4jRI6vAXNXWqWjfrXw=
github.com/metacubex/quic-go v0.42.1-0.20240319071510-a251e5c66a5c h1:AhaPKvVqF3N/jXFmlW51Cf1+KddslKAsZqcdgGhZjr0= github.com/metacubex/quic-go v0.42.1-0.20240418003344-f006b5735d98 h1:oMLlJV4a9AylNo8ZLBNUiqZ02Vme6GLLHjuWJz8amSk=
github.com/metacubex/quic-go v0.42.1-0.20240319071510-a251e5c66a5c/go.mod h1:iGx3Y1zynls/FjFgykLSqDcM81U0IKePRTXEz5g3iiQ= github.com/metacubex/quic-go v0.42.1-0.20240418003344-f006b5735d98/go.mod h1:iGx3Y1zynls/FjFgykLSqDcM81U0IKePRTXEz5g3iiQ=
github.com/metacubex/sing v0.0.0-20240313064558-c197257f6542 h1:e9nBnrJBv3HzZVeSzJN0G2SADjebd2ZLF1F5dmsjUTc= github.com/metacubex/sing v0.0.0-20240408015159-aa61c96df764 h1:+czGKoynxYA90YaL3NlCAIJHnlqwoUlLWgmOhdm5ZU8=
github.com/metacubex/sing v0.0.0-20240313064558-c197257f6542/go.mod h1:+60H3Cm91RnL9dpVGWDPHt0zTQImO9Vfqt9a4rSambI= github.com/metacubex/sing v0.0.0-20240408015159-aa61c96df764/go.mod h1:+60H3Cm91RnL9dpVGWDPHt0zTQImO9Vfqt9a4rSambI=
github.com/metacubex/sing-quic v0.0.0-20240310154810-47bca850fc01 h1:5INHs85Gp1JZsdF7fQp1pXUjfJOX2dhwZjuUQWJVSt8= github.com/metacubex/sing-quic v0.0.0-20240418004036-814c531c378d h1:RAe0ND8J5SOPGI623oEXfaHKaaUrrzHx+U1DN9Awcco=
github.com/metacubex/sing-quic v0.0.0-20240310154810-47bca850fc01/go.mod h1:WyY0zYxv+o+18R/Ece+QFontlgXoobKbNqbtYn2zjz8= github.com/metacubex/sing-quic v0.0.0-20240418004036-814c531c378d/go.mod h1:WyY0zYxv+o+18R/Ece+QFontlgXoobKbNqbtYn2zjz8=
github.com/metacubex/sing-shadowsocks v0.2.6 h1:6oEB3QcsFYnNiFeoevcXrCwJ3sAablwVSgtE9R3QeFQ= github.com/metacubex/sing-shadowsocks v0.2.6 h1:6oEB3QcsFYnNiFeoevcXrCwJ3sAablwVSgtE9R3QeFQ=
github.com/metacubex/sing-shadowsocks v0.2.6/go.mod h1:zIkMeSnb8Mbf4hdqhw0pjzkn1d99YJ3JQm/VBg5WMTg= github.com/metacubex/sing-shadowsocks v0.2.6/go.mod h1:zIkMeSnb8Mbf4hdqhw0pjzkn1d99YJ3JQm/VBg5WMTg=
github.com/metacubex/sing-shadowsocks2 v0.2.0 h1:hqwT/AfI5d5UdPefIzR6onGHJfDXs5zgOM5QSgaM/9A= github.com/metacubex/sing-shadowsocks2 v0.2.0 h1:hqwT/AfI5d5UdPefIzR6onGHJfDXs5zgOM5QSgaM/9A=
github.com/metacubex/sing-shadowsocks2 v0.2.0/go.mod h1:LCKF6j1P94zN8ZS+LXRK1gmYTVGB3squivBSXAFnOg8= github.com/metacubex/sing-shadowsocks2 v0.2.0/go.mod h1:LCKF6j1P94zN8ZS+LXRK1gmYTVGB3squivBSXAFnOg8=
github.com/metacubex/sing-tun v0.2.1-0.20240320004934-5d2b35447bfd h1:NgLb6Lvr8ZxX0inWswVYjal2SUzsJJ54dFQNOluUJuE= github.com/metacubex/sing-tun v0.2.6 h1:frc58BqnIClqcC9KcYBfVAn5bgO6WW1ANKvZW3/HYAQ=
github.com/metacubex/sing-tun v0.2.1-0.20240320004934-5d2b35447bfd/go.mod h1:GfLZG/QgGpW9+BPjltzONrL5vVms86TWqmZ23J68ISc= github.com/metacubex/sing-tun v0.2.6/go.mod h1:4VsMwZH1IlgPGFK1ZbBomZ/B2MYkTgs2+gnBAr5GOIo=
github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f h1:QjXrHKbTMBip/C+R79bvbfr42xH1gZl3uFb0RELdZiQ= github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f h1:QjXrHKbTMBip/C+R79bvbfr42xH1gZl3uFb0RELdZiQ=
github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY= github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY=
github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63 h1:AGyIB55UfQm/0ZH0HtQO9u3l//yjtHUpjeRjjPGfGRI= github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63 h1:AGyIB55UfQm/0ZH0HtQO9u3l//yjtHUpjeRjjPGfGRI=
github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63/go.mod h1:uY+BYb0UEknLrqvbGcwi9i++KgrKxsurysgI6G1Pveo= github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63/go.mod h1:uY+BYb0UEknLrqvbGcwi9i++KgrKxsurysgI6G1Pveo=
github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 h1:as/aO/fM8nv4W4pOr9EETP6kV/Oaujk3fUNyQSJK61c= github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 h1:as/aO/fM8nv4W4pOr9EETP6kV/Oaujk3fUNyQSJK61c=
github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts= github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts=
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU= github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU= github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4= github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4=
@ -169,8 +171,8 @@ github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e h1:iGH0RMv2F
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e/go.mod h1:YbL4TKHRR6APYQv3U2RGfwLDpPYSyWz6oUlpISBEzBE= github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e/go.mod h1:YbL4TKHRR6APYQv3U2RGfwLDpPYSyWz6oUlpISBEzBE=
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/shirou/gopsutil/v3 v3.24.2 h1:kcR0erMbLg5/3LcInpw0X/rrPSqq4CDPyI6A6ZRC18Y= github.com/shirou/gopsutil/v3 v3.24.3 h1:eoUGJSmdfLzJ3mxIhmOAhgKEKgQkeOwKpz1NbhVnuPE=
github.com/shirou/gopsutil/v3 v3.24.2/go.mod h1:tSg/594BcA+8UdQU2XcW803GWYgdtauFFPgJCJKZlVk= github.com/shirou/gopsutil/v3 v3.24.3/go.mod h1:JpND7O217xa72ewWz9zN2eIIkPWsDN/3pl0H8Qt0uwg=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
@ -210,8 +212,8 @@ github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zhangyunhao116/fastrand v0.3.0 h1:7bwe124xcckPulX6fxtr2lFdO2KQqaefdtbk+mqO/Ig= github.com/zhangyunhao116/fastrand v0.4.0 h1:86QB6Y+GGgLZRFRDCjMmAS28QULwspK9sgL5d1Bx3H4=
github.com/zhangyunhao116/fastrand v0.3.0/go.mod h1:0v5KgHho0VE6HU192HnY15de/oDS8UrbBChIFjIhBtc= github.com/zhangyunhao116/fastrand v0.4.0/go.mod h1:vIyo6EyBhjGKpZv6qVlkPl4JVAklpMM4DSKzbAkMguA=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ=
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
@ -222,21 +224,21 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -252,18 +254,18 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
@ -273,5 +275,5 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= lukechampine.com/blake3 v1.2.2 h1:wEAbSg0IVU4ih44CVlpMqMZMpzr5hf/6aqodLlevd/w=
lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= lukechampine.com/blake3 v1.2.2/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=

View File

@ -21,6 +21,12 @@ func WithExternalController(externalController string) Option {
} }
} }
func WithExternalControllerUnix(externalControllerUnix string) Option {
return func(cfg *config.Config) {
cfg.General.ExternalControllerUnix = externalControllerUnix
}
}
func WithSecret(secret string) Option { func WithSecret(secret string) Option {
return func(cfg *config.Config) { return func(cfg *config.Config) {
cfg.General.Secret = secret cfg.General.Secret = secret
@ -47,6 +53,10 @@ func Parse(options ...Option) error {
cfg.General.Secret, cfg.TLS.Certificate, cfg.TLS.PrivateKey, cfg.General.LogLevel == log.DEBUG) cfg.General.Secret, cfg.TLS.Certificate, cfg.TLS.PrivateKey, cfg.General.LogLevel == log.DEBUG)
} }
if cfg.General.ExternalControllerUnix != "" {
go route.StartUnix(cfg.General.ExternalControllerUnix, cfg.General.LogLevel == log.DEBUG)
}
executor.ApplyConfig(cfg, true) executor.ApplyConfig(cfg, true)
return nil return nil
} }

View File

@ -7,8 +7,11 @@ import (
"encoding/json" "encoding/json"
"net" "net"
"net/http" "net/http"
"os"
"path/filepath"
"runtime/debug" "runtime/debug"
"strings" "strings"
"syscall"
"time" "time"
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
@ -47,15 +50,7 @@ func SetUIPath(path string) {
uiPath = C.Path.Resolve(path) uiPath = C.Path.Resolve(path)
} }
func Start(addr string, tlsAddr string, secret string, func router(isDebug bool, withAuth bool) *chi.Mux {
certificat, privateKey string, isDebug bool) {
if serverAddr != "" {
return
}
serverAddr = addr
serverSecret = secret
r := chi.NewRouter() r := chi.NewRouter()
corsM := cors.New(cors.Options{ corsM := cors.New(cors.Options{
AllowedOrigins: []string{"*"}, AllowedOrigins: []string{"*"},
@ -77,7 +72,9 @@ func Start(addr string, tlsAddr string, secret string,
}()) }())
} }
r.Group(func(r chi.Router) { r.Group(func(r chi.Router) {
r.Use(authentication) if withAuth {
r.Use(authentication)
}
r.Get("/", hello) r.Get("/", hello)
r.Get("/logs", getLogs) r.Get("/logs", getLogs)
r.Get("/traffic", traffic) r.Get("/traffic", traffic)
@ -107,10 +104,21 @@ func Start(addr string, tlsAddr string, secret string,
}) })
}) })
} }
return r
}
func Start(addr string, tlsAddr string, secret string,
certificate, privateKey string, isDebug bool) {
if serverAddr != "" {
return
}
serverAddr = addr
serverSecret = secret
if len(tlsAddr) > 0 { if len(tlsAddr) > 0 {
go func() { go func() {
c, err := CN.ParseCert(certificat, privateKey, C.Path) c, err := CN.ParseCert(certificate, privateKey, C.Path)
if err != nil { if err != nil {
log.Errorln("External controller tls listen error: %s", err) log.Errorln("External controller tls listen error: %s", err)
return return
@ -125,7 +133,7 @@ func Start(addr string, tlsAddr string, secret string,
serverAddr = l.Addr().String() serverAddr = l.Addr().String()
log.Infoln("RESTful API tls listening at: %s", serverAddr) log.Infoln("RESTful API tls listening at: %s", serverAddr)
tlsServe := &http.Server{ tlsServe := &http.Server{
Handler: r, Handler: router(isDebug, true),
TLSConfig: &tls.Config{ TLSConfig: &tls.Config{
Certificates: []tls.Certificate{c}, Certificates: []tls.Certificate{c},
}, },
@ -144,12 +152,45 @@ func Start(addr string, tlsAddr string, secret string,
serverAddr = l.Addr().String() serverAddr = l.Addr().String()
log.Infoln("RESTful API listening at: %s", serverAddr) log.Infoln("RESTful API listening at: %s", serverAddr)
if err = http.Serve(l, r); err != nil { if err = http.Serve(l, router(isDebug, true)); err != nil {
log.Errorln("External controller serve error: %s", err) log.Errorln("External controller serve error: %s", err)
} }
} }
func StartUnix(addr string, isDebug bool) {
addr = C.Path.Resolve(addr)
dir := filepath.Dir(addr)
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err := os.MkdirAll(dir, 0o755); err != nil {
log.Errorln("External controller unix listen error: %s", err)
return
}
}
// https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/
//
// Note: As mentioned above in the security section, when a socket binds a socket to a valid pathname address,
// a socket file is created within the filesystem. On Linux, the application is expected to unlink
// (see the notes section in the man page for AF_UNIX) before any other socket can be bound to the same address.
// The same applies to Windows unix sockets, except that, DeleteFile (or any other file delete API)
// should be used to delete the socket file prior to calling bind with the same path.
_ = syscall.Unlink(addr)
l, err := inbound.Listen("unix", addr)
if err != nil {
log.Errorln("External controller unix listen error: %s", err)
return
}
serverAddr = l.Addr().String()
log.Infoln("RESTful API unix listening at: %s", serverAddr)
if err = http.Serve(l, router(isDebug, false)); err != nil {
log.Errorln("External controller unix serve error: %s", err)
}
}
func setPrivateNetworkAccess(next http.Handler) http.Handler { func setPrivateNetworkAccess(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" { if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" {

View File

@ -51,8 +51,9 @@ func HandleConn(c net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool],
var resp *http.Response var resp *http.Response
if !trusted { if !trusted {
resp = authenticate(request, cache) var user string
resp, user = authenticate(request, cache)
additions = append(additions, inbound.WithInUser(user))
trusted = resp == nil trusted = resp == nil
} }
@ -130,7 +131,7 @@ func HandleConn(c net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool],
_ = conn.Close() _ = conn.Close()
} }
func authenticate(request *http.Request, cache *lru.LruCache[string, bool]) *http.Response { func authenticate(request *http.Request, cache *lru.LruCache[string, bool]) (resp *http.Response, u string) {
authenticator := authStore.Authenticator() authenticator := authStore.Authenticator()
if inbound.SkipAuthRemoteAddress(request.RemoteAddr) { if inbound.SkipAuthRemoteAddress(request.RemoteAddr) {
authenticator = nil authenticator = nil
@ -140,23 +141,24 @@ func authenticate(request *http.Request, cache *lru.LruCache[string, bool]) *htt
if credential == "" { if credential == "" {
resp := responseWith(request, http.StatusProxyAuthRequired) resp := responseWith(request, http.StatusProxyAuthRequired)
resp.Header.Set("Proxy-Authenticate", "Basic") resp.Header.Set("Proxy-Authenticate", "Basic")
return resp return resp, ""
} }
authed, exist := cache.Get(credential) authed, exist := cache.Get(credential)
if !exist { if !exist {
user, pass, err := decodeBasicProxyAuthorization(credential) user, pass, err := decodeBasicProxyAuthorization(credential)
authed = err == nil && authenticator.Verify(user, pass) authed = err == nil && authenticator.Verify(user, pass)
u = user
cache.Set(credential, authed) cache.Set(credential, authed)
} }
if !authed { if !authed {
log.Infoln("Auth failed from %s", request.RemoteAddr) log.Infoln("Auth failed from %s", request.RemoteAddr)
return responseWith(request, http.StatusForbidden) return responseWith(request, http.StatusForbidden), u
} }
} }
return nil return nil, u
} }
func responseWith(request *http.Request, statusCode int) *http.Response { func responseWith(request *http.Request, statusCode int) *http.Response {

View File

@ -16,7 +16,7 @@ func New(t C.Tunnel) {
tunnel = t tunnel = t
} }
func HandleTcp(address string) (conn net.Conn, err error) { func HandleTcp(address string, proxy string) (conn net.Conn, err error) {
if tunnel == nil { if tunnel == nil {
return nil, errors.New("tcp uninitialized") return nil, errors.New("tcp uninitialized")
} }
@ -28,6 +28,9 @@ func HandleTcp(address string) (conn net.Conn, err error) {
metadata.Type = C.INNER metadata.Type = C.INNER
metadata.DNSMode = C.DNSNormal metadata.DNSMode = C.DNSNormal
metadata.Process = C.MihomoName metadata.Process = C.MihomoName
if proxy != "" {
metadata.SpecialProxy = proxy
}
if h, port, err := net.SplitHostPort(address); err == nil { if h, port, err := net.SplitHostPort(address); err == nil {
if port, err := strconv.ParseUint(port, 10, 16); err == nil { if port, err := strconv.ParseUint(port, 10, 16); err == nil {
metadata.DstPort = uint16(port) metadata.DstPort = uint16(port)

View File

@ -12,6 +12,7 @@ import (
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/iface" "github.com/metacubex/mihomo/component/iface"
"github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config" LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/sing" "github.com/metacubex/mihomo/listener/sing"
@ -39,6 +40,8 @@ type Listener struct {
networkUpdateMonitor tun.NetworkUpdateMonitor networkUpdateMonitor tun.NetworkUpdateMonitor
defaultInterfaceMonitor tun.DefaultInterfaceMonitor defaultInterfaceMonitor tun.DefaultInterfaceMonitor
packageManager tun.PackageManager packageManager tun.PackageManager
dnsServerIp []string
} }
func CalculateInterfaceName(name string) (tunName string) { func CalculateInterfaceName(name string) (tunName string) {
@ -147,12 +150,16 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
dnsAdds = append(dnsAdds, addrPort) dnsAdds = append(dnsAdds, addrPort)
} }
var dnsServerIp []string
for _, a := range options.Inet4Address { for _, a := range options.Inet4Address {
addrPort := netip.AddrPortFrom(a.Addr().Next(), 53) addrPort := netip.AddrPortFrom(a.Addr().Next(), 53)
dnsServerIp = append(dnsServerIp, a.Addr().Next().String())
dnsAdds = append(dnsAdds, addrPort) dnsAdds = append(dnsAdds, addrPort)
} }
for _, a := range options.Inet6Address { for _, a := range options.Inet6Address {
addrPort := netip.AddrPortFrom(a.Addr().Next(), 53) addrPort := netip.AddrPortFrom(a.Addr().Next(), 53)
dnsServerIp = append(dnsServerIp, a.Addr().Next().String())
dnsAdds = append(dnsAdds, addrPort) dnsAdds = append(dnsAdds, addrPort)
} }
@ -244,6 +251,10 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
return return
} }
l.dnsServerIp = dnsServerIp
// after tun.New sing-tun has set DNS to TUN interface
resolver.AddSystemDnsBlacklist(dnsServerIp...)
stackOptions := tun.StackOptions{ stackOptions := tun.StackOptions{
Context: context.TODO(), Context: context.TODO(),
Tun: tunIf, Tun: tunIf,
@ -261,15 +272,17 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
} }
} }
l.tunIf = tunIf l.tunIf = tunIf
l.tunStack, err = tun.NewStack(strings.ToLower(options.Stack.String()), stackOptions)
tunStack, err := tun.NewStack(strings.ToLower(options.Stack.String()), stackOptions)
if err != nil { if err != nil {
return return
} }
err = l.tunStack.Start() err = tunStack.Start()
if err != nil { if err != nil {
return return
} }
l.tunStack = tunStack
//l.openAndroidHotspot(tunOptions) //l.openAndroidHotspot(tunOptions)
@ -334,6 +347,7 @@ func parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges.
func (l *Listener) Close() error { func (l *Listener) Close() error {
l.closed = true l.closed = true
resolver.RemoveSystemDnsBlacklist(l.dnsServerIp...)
return common.Close( return common.Close(
l.tunStack, l.tunStack,
l.tunIf, l.tunIf,

View File

@ -98,11 +98,12 @@ func HandleSocks4(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition)
if inbound.SkipAuthRemoteAddr(conn.RemoteAddr()) { if inbound.SkipAuthRemoteAddr(conn.RemoteAddr()) {
authenticator = nil authenticator = nil
} }
addr, _, err := socks4.ServerHandshake(conn, authenticator) addr, _, user, err := socks4.ServerHandshake(conn, authenticator)
if err != nil { if err != nil {
conn.Close() conn.Close()
return return
} }
additions = append(additions, inbound.WithInUser(user))
tunnel.HandleTCPConn(inbound.NewSocket(socks5.ParseAddr(addr), conn, C.SOCKS4, additions...)) tunnel.HandleTCPConn(inbound.NewSocket(socks5.ParseAddr(addr), conn, C.SOCKS4, additions...))
} }
@ -111,7 +112,7 @@ func HandleSocks5(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition)
if inbound.SkipAuthRemoteAddr(conn.RemoteAddr()) { if inbound.SkipAuthRemoteAddr(conn.RemoteAddr()) {
authenticator = nil authenticator = nil
} }
target, command, err := socks5.ServerHandshake(conn, authenticator) target, command, user, err := socks5.ServerHandshake(conn, authenticator)
if err != nil { if err != nil {
conn.Close() conn.Close()
return return
@ -121,5 +122,6 @@ func HandleSocks5(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition)
io.Copy(io.Discard, conn) io.Copy(io.Discard, conn)
return return
} }
additions = append(additions, inbound.WithInUser(user))
tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.SOCKS5, additions...)) tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.SOCKS5, additions...))
} }

View File

@ -34,6 +34,8 @@ func (l *Listener) Close() error {
func (l *Listener) handleTProxy(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) { func (l *Listener) handleTProxy(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) {
target := socks5.ParseAddrToSocksAddr(conn.LocalAddr()) target := socks5.ParseAddrToSocksAddr(conn.LocalAddr())
N.TCPKeepAlive(conn) N.TCPKeepAlive(conn)
// TProxy's conn.LocalAddr() is target address, so we set from l.listener
additions = append([]inbound.Addition{inbound.WithInAddr(l.listener.Addr())}, additions...)
tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.TPROXY, additions...)) tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.TPROXY, additions...))
} }

25
main.go
View File

@ -23,16 +23,17 @@ import (
) )
var ( var (
version bool version bool
testConfig bool testConfig bool
geodataMode bool geodataMode bool
homeDir string homeDir string
configFile string configFile string
externalUI string externalUI string
externalController string externalController string
secret string externalControllerUnix string
updateGeoMux sync.Mutex secret string
updatingGeo = false updateGeoMux sync.Mutex
updatingGeo = false
) )
func init() { func init() {
@ -40,6 +41,7 @@ func init() {
flag.StringVar(&configFile, "f", os.Getenv("CLASH_CONFIG_FILE"), "specify configuration file") flag.StringVar(&configFile, "f", os.Getenv("CLASH_CONFIG_FILE"), "specify configuration file")
flag.StringVar(&externalUI, "ext-ui", os.Getenv("CLASH_OVERRIDE_EXTERNAL_UI_DIR"), "override external ui directory") flag.StringVar(&externalUI, "ext-ui", os.Getenv("CLASH_OVERRIDE_EXTERNAL_UI_DIR"), "override external ui directory")
flag.StringVar(&externalController, "ext-ctl", os.Getenv("CLASH_OVERRIDE_EXTERNAL_CONTROLLER"), "override external controller address") flag.StringVar(&externalController, "ext-ctl", os.Getenv("CLASH_OVERRIDE_EXTERNAL_CONTROLLER"), "override external controller address")
flag.StringVar(&externalControllerUnix, "ext-ctl-unix", os.Getenv("CLASH_OVERRIDE_EXTERNAL_CONTROLLER_UNIX"), "override external controller unix address")
flag.StringVar(&secret, "secret", os.Getenv("CLASH_OVERRIDE_SECRET"), "override secret for RESTful API") flag.StringVar(&secret, "secret", os.Getenv("CLASH_OVERRIDE_SECRET"), "override secret for RESTful API")
flag.BoolVar(&geodataMode, "m", false, "set geodata mode") flag.BoolVar(&geodataMode, "m", false, "set geodata mode")
flag.BoolVar(&version, "v", false, "show current version of mihomo") flag.BoolVar(&version, "v", false, "show current version of mihomo")
@ -102,6 +104,9 @@ func main() {
if externalController != "" { if externalController != "" {
options = append(options, hub.WithExternalController(externalController)) options = append(options, hub.WithExternalController(externalController))
} }
if externalControllerUnix != "" {
options = append(options, hub.WithExternalControllerUnix(externalControllerUnix))
}
if secret != "" { if secret != "" {
options = append(options, hub.WithSecret(secret)) options = append(options, hub.WithSecret(secret))
} }

View File

@ -4,6 +4,7 @@ import (
"strings" "strings"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"golang.org/x/net/idna"
) )
type Domain struct { type Domain struct {
@ -29,9 +30,10 @@ func (d *Domain) Payload() string {
} }
func NewDomain(domain string, adapter string) *Domain { func NewDomain(domain string, adapter string) *Domain {
punycode, _ := idna.ToASCII(strings.ToLower(domain))
return &Domain{ return &Domain{
Base: &Base{}, Base: &Base{},
domain: strings.ToLower(domain), domain: punycode,
adapter: adapter, adapter: adapter,
} }
} }

View File

@ -4,6 +4,7 @@ import (
"strings" "strings"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"golang.org/x/net/idna"
) )
type DomainKeyword struct { type DomainKeyword struct {
@ -30,9 +31,10 @@ func (dk *DomainKeyword) Payload() string {
} }
func NewDomainKeyword(keyword string, adapter string) *DomainKeyword { func NewDomainKeyword(keyword string, adapter string) *DomainKeyword {
punycode, _ := idna.ToASCII(strings.ToLower(keyword))
return &DomainKeyword{ return &DomainKeyword{
Base: &Base{}, Base: &Base{},
keyword: strings.ToLower(keyword), keyword: punycode,
adapter: adapter, adapter: adapter,
} }
} }

View File

@ -4,6 +4,7 @@ import (
"strings" "strings"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"golang.org/x/net/idna"
) )
type DomainSuffix struct { type DomainSuffix struct {
@ -30,9 +31,10 @@ func (ds *DomainSuffix) Payload() string {
} }
func NewDomainSuffix(suffix string, adapter string) *DomainSuffix { func NewDomainSuffix(suffix string, adapter string) *DomainSuffix {
punycode, _ := idna.ToASCII(strings.ToLower(suffix))
return &DomainSuffix{ return &DomainSuffix{
Base: &Base{}, Base: &Base{},
suffix: strings.ToLower(suffix), suffix: punycode,
adapter: adapter, adapter: adapter,
} }
} }

View File

@ -17,6 +17,7 @@ type GEOIP struct {
country string country string
adapter string adapter string
noResolveIP bool noResolveIP bool
isSourceIP bool
geoIPMatcher *router.GeoIPMatcher geoIPMatcher *router.GeoIPMatcher
recodeSize int recodeSize int
} }
@ -24,11 +25,17 @@ type GEOIP struct {
var _ C.Rule = (*GEOIP)(nil) var _ C.Rule = (*GEOIP)(nil)
func (g *GEOIP) RuleType() C.RuleType { func (g *GEOIP) RuleType() C.RuleType {
if g.isSourceIP {
return C.SrcGEOIP
}
return C.GEOIP return C.GEOIP
} }
func (g *GEOIP) Match(metadata *C.Metadata) (bool, string) { func (g *GEOIP) Match(metadata *C.Metadata) (bool, string) {
ip := metadata.DstIP ip := metadata.DstIP
if g.isSourceIP {
ip = metadata.SrcIP
}
if !ip.IsValid() { if !ip.IsValid() {
return false, "" return false, ""
} }
@ -49,6 +56,16 @@ func (g *GEOIP) Match(metadata *C.Metadata) (bool, string) {
} }
if !C.GeodataMode { if !C.GeodataMode {
if g.isSourceIP {
codes := mmdb.IPInstance().LookupCode(ip.AsSlice())
for _, code := range codes {
if g.country == code {
return true, g.adapter
}
}
return false, g.adapter
}
if metadata.DstGeoIP != nil { if metadata.DstGeoIP != nil {
return false, g.adapter return false, g.adapter
} }
@ -62,7 +79,7 @@ func (g *GEOIP) Match(metadata *C.Metadata) (bool, string) {
} }
match := g.geoIPMatcher.Match(ip) match := g.geoIPMatcher.Match(ip)
if match { if match && !g.isSourceIP {
metadata.DstGeoIP = append(metadata.DstGeoIP, g.country) metadata.DstGeoIP = append(metadata.DstGeoIP, g.country)
} }
return match, g.adapter return match, g.adapter
@ -92,7 +109,7 @@ func (g *GEOIP) GetRecodeSize() int {
return g.recodeSize return g.recodeSize
} }
func NewGEOIP(country string, adapter string, noResolveIP bool) (*GEOIP, error) { func NewGEOIP(country string, adapter string, isSrc, noResolveIP bool) (*GEOIP, error) {
if err := geodata.InitGeoIP(); err != nil { if err := geodata.InitGeoIP(); err != nil {
log.Errorln("can't initial GeoIP: %s", err) log.Errorln("can't initial GeoIP: %s", err)
return nil, err return nil, err
@ -105,6 +122,7 @@ func NewGEOIP(country string, adapter string, noResolveIP bool) (*GEOIP, error)
country: country, country: country,
adapter: adapter, adapter: adapter,
noResolveIP: noResolveIP, noResolveIP: noResolveIP,
isSourceIP: isSrc,
} }
return geoip, nil return geoip, nil
} }
@ -120,6 +138,7 @@ func NewGEOIP(country string, adapter string, noResolveIP bool) (*GEOIP, error)
country: country, country: country,
adapter: adapter, adapter: adapter,
noResolveIP: noResolveIP, noResolveIP: noResolveIP,
isSourceIP: isSrc,
geoIPMatcher: geoIPMatcher, geoIPMatcher: geoIPMatcher,
recodeSize: size, recodeSize: size,
} }

View File

@ -14,24 +14,32 @@ type ASN struct {
asn string asn string
adapter string adapter string
noResolveIP bool noResolveIP bool
isSourceIP bool
} }
func (a *ASN) Match(metadata *C.Metadata) (bool, string) { func (a *ASN) Match(metadata *C.Metadata) (bool, string) {
ip := metadata.DstIP ip := metadata.DstIP
if a.isSourceIP {
ip = metadata.SrcIP
}
if !ip.IsValid() { if !ip.IsValid() {
return false, "" return false, ""
} }
result := mmdb.ASNInstance().LookupASN(ip.AsSlice()) result := mmdb.ASNInstance().LookupASN(ip.AsSlice())
asnNumber := strconv.FormatUint(uint64(result.AutonomousSystemNumber), 10) asnNumber := strconv.FormatUint(uint64(result.AutonomousSystemNumber), 10)
metadata.DstIPASN = asnNumber + " " + result.AutonomousSystemOrganization if !a.isSourceIP {
metadata.DstIPASN = asnNumber + " " + result.AutonomousSystemOrganization
}
match := a.asn == asnNumber match := a.asn == asnNumber
return match, a.adapter return match, a.adapter
} }
func (a *ASN) RuleType() C.RuleType { func (a *ASN) RuleType() C.RuleType {
if a.isSourceIP {
return C.SrcIPASN
}
return C.IPASN return C.IPASN
} }
@ -51,7 +59,7 @@ func (a *ASN) GetASN() string {
return a.asn return a.asn
} }
func NewIPASN(asn string, adapter string, noResolveIP bool) (*ASN, error) { func NewIPASN(asn string, adapter string, isSrc, noResolveIP bool) (*ASN, error) {
C.ASNEnable = true C.ASNEnable = true
if err := geodata.InitASN(); err != nil { if err := geodata.InitASN(); err != nil {
log.Errorln("can't initial ASN: %s", err) log.Errorln("can't initial ASN: %s", err)
@ -63,5 +71,6 @@ func NewIPASN(asn string, adapter string, noResolveIP bool) (*ASN, error) {
asn: asn, asn: asn,
adapter: adapter, adapter: adapter,
noResolveIP: noResolveIP, noResolveIP: noResolveIP,
isSourceIP: isSrc,
}, nil }, nil
} }

View File

@ -23,13 +23,17 @@ func ParseRule(tp, payload, target string, params []string, subRules map[string]
parsed, parseErr = RC.NewGEOSITE(payload, target) parsed, parseErr = RC.NewGEOSITE(payload, target)
case "GEOIP": case "GEOIP":
noResolve := RC.HasNoResolve(params) noResolve := RC.HasNoResolve(params)
parsed, parseErr = RC.NewGEOIP(payload, target, noResolve) parsed, parseErr = RC.NewGEOIP(payload, target, false, noResolve)
case "SRC-GEOIP":
parsed, parseErr = RC.NewGEOIP(payload, target, true, true)
case "IP-ASN":
noResolve := RC.HasNoResolve(params)
parsed, parseErr = RC.NewIPASN(payload, target, false, noResolve)
case "SRC-IP-ASN":
parsed, parseErr = RC.NewIPASN(payload, target, true, true)
case "IP-CIDR", "IP-CIDR6": case "IP-CIDR", "IP-CIDR6":
noResolve := RC.HasNoResolve(params) noResolve := RC.HasNoResolve(params)
parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRNoResolve(noResolve)) parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRNoResolve(noResolve))
case "IP-ASN":
noResolve := RC.HasNoResolve(params)
parsed, parseErr = RC.NewIPASN(payload, target, noResolve)
case "SRC-IP-CIDR": case "SRC-IP-CIDR":
parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true)) parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true))
case "IP-SUFFIX": case "IP-SUFFIX":

View File

@ -21,6 +21,7 @@ type ruleProviderSchema struct {
Behavior string `provider:"behavior"` Behavior string `provider:"behavior"`
Path string `provider:"path,omitempty"` Path string `provider:"path,omitempty"`
URL string `provider:"url,omitempty"` URL string `provider:"url,omitempty"`
Proxy string `provider:"proxy,omitempty"`
Format string `provider:"format,omitempty"` Format string `provider:"format,omitempty"`
Interval int `provider:"interval,omitempty"` Interval int `provider:"interval,omitempty"`
} }
@ -61,17 +62,14 @@ func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(t
path := C.Path.Resolve(schema.Path) path := C.Path.Resolve(schema.Path)
vehicle = resource.NewFileVehicle(path) vehicle = resource.NewFileVehicle(path)
case "http": case "http":
path := C.Path.GetPathByHash("rules", schema.URL)
if schema.Path != "" { if schema.Path != "" {
path := C.Path.Resolve(schema.Path) path = C.Path.Resolve(schema.Path)
if !features.CMFA && !C.Path.IsSafePath(path) { if !features.CMFA && !C.Path.IsSafePath(path) {
return nil, fmt.Errorf("%w: %s", errSubPath, path) return nil, fmt.Errorf("%w: %s", errSubPath, path)
} }
vehicle = resource.NewHTTPVehicle(schema.URL, path)
} else {
path := C.Path.GetPathByHash("rules", schema.URL)
vehicle = resource.NewHTTPVehicle(schema.URL, path)
} }
vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, nil)
default: default:
return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type) return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type)
} }

View File

@ -43,7 +43,7 @@ var (
var subnet = netip.PrefixFrom(netip.IPv4Unspecified(), 24) var subnet = netip.PrefixFrom(netip.IPv4Unspecified(), 24)
func ServerHandshake(rw io.ReadWriter, authenticator auth.Authenticator) (addr string, command Command, err error) { func ServerHandshake(rw io.ReadWriter, authenticator auth.Authenticator) (addr string, command Command, user string, err error) {
var req [8]byte var req [8]byte
if _, err = io.ReadFull(rw, req[:]); err != nil { if _, err = io.ReadFull(rw, req[:]); err != nil {
return return
@ -73,6 +73,7 @@ func ServerHandshake(rw io.ReadWriter, authenticator auth.Authenticator) (addr s
if userID, err = readUntilNull(rw); err != nil { if userID, err = readUntilNull(rw); err != nil {
return return
} }
user = string(userID)
if isReservedIP(dstIP) { if isReservedIP(dstIP) {
var target []byte var target []byte
@ -90,7 +91,7 @@ func ServerHandshake(rw io.ReadWriter, authenticator auth.Authenticator) (addr s
} }
// SOCKS4 only support USERID auth. // SOCKS4 only support USERID auth.
if authenticator == nil || authenticator.Verify(string(userID), "") { if authenticator == nil || authenticator.Verify(user, "") {
code = RequestGranted code = RequestGranted
} else { } else {
code = RequestIdentdMismatched code = RequestIdentdMismatched

View File

@ -106,7 +106,7 @@ type User struct {
} }
// ServerHandshake fast-tracks SOCKS initialization to get target address to connect on server side. // ServerHandshake fast-tracks SOCKS initialization to get target address to connect on server side.
func ServerHandshake(rw net.Conn, authenticator auth.Authenticator) (addr Addr, command Command, err error) { func ServerHandshake(rw net.Conn, authenticator auth.Authenticator) (addr Addr, command Command, user string, err error) {
// Read RFC 1928 for request and reply structure and sizes. // Read RFC 1928 for request and reply structure and sizes.
buf := make([]byte, MaxAddrLen) buf := make([]byte, MaxAddrLen)
// read VER, NMETHODS, METHODS // read VER, NMETHODS, METHODS
@ -141,7 +141,7 @@ func ServerHandshake(rw net.Conn, authenticator auth.Authenticator) (addr Addr,
if _, err = io.ReadFull(rw, authBuf[:userLen]); err != nil { if _, err = io.ReadFull(rw, authBuf[:userLen]); err != nil {
return return
} }
user := string(authBuf[:userLen]) user = string(authBuf[:userLen])
// Get password // Get password
if _, err = rw.Read(header[:1]); err != nil { if _, err = rw.Read(header[:1]); err != nil {

View File

@ -12,6 +12,7 @@ import (
"time" "time"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/loopback"
"github.com/metacubex/mihomo/component/nat" "github.com/metacubex/mihomo/component/nat"
P "github.com/metacubex/mihomo/component/process" P "github.com/metacubex/mihomo/component/process"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
@ -694,6 +695,9 @@ func shouldStopRetry(err error) bool {
if errors.Is(err, resolver.ErrIPv6Disabled) { if errors.Is(err, resolver.ErrIPv6Disabled) {
return true return true
} }
if errors.Is(err, loopback.ErrReject) {
return true
}
return false return false
} }