diff --git a/.github/update.log b/.github/update.log index b1b05cf8ae..eb50e3e587 100644 --- a/.github/update.log +++ b/.github/update.log @@ -1201,3 +1201,4 @@ Update On Sun Nov 30 19:38:16 CET 2025 Update On Mon Dec 1 19:44:38 CET 2025 Update On Tue Dec 2 19:43:40 CET 2025 Update On Wed Dec 3 19:42:45 CET 2025 +Update On Thu Dec 4 19:44:01 CET 2025 diff --git a/clash-meta/adapter/adapter.go b/clash-meta/adapter/adapter.go index ef8d4ee347..815064568b 100644 --- a/clash-meta/adapter/adapter.go +++ b/clash-meta/adapter/adapter.go @@ -153,8 +153,9 @@ func (p *Proxy) MarshalJSON() ([]byte, error) { mapping["mptcp"] = proxyInfo.MPTCP mapping["smux"] = proxyInfo.SMUX mapping["interface"] = proxyInfo.Interface - mapping["dialer-proxy"] = proxyInfo.DialerProxy mapping["routing-mark"] = proxyInfo.RoutingMark + mapping["provider-name"] = proxyInfo.ProviderName + mapping["dialer-proxy"] = proxyInfo.DialerProxy return json.Marshal(mapping) } @@ -177,14 +178,12 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In p.history.Pop() } - state, ok := p.extra.Load(url) - if !ok { - state = &internalProxyState{ + state, _ := p.extra.LoadOrStoreFn(url, func() *internalProxyState { + return &internalProxyState{ history: queue.New[C.DelayHistory](defaultHistoriesNum), alive: atomic.NewBool(true), } - p.extra.Store(url, state) - } + }) if !satisfied { record.Delay = 0 diff --git a/clash-meta/adapter/outbound/anytls.go b/clash-meta/adapter/outbound/anytls.go index 5ba7c6d265..c17f27d55e 100644 --- a/clash-meta/adapter/outbound/anytls.go +++ b/clash-meta/adapter/outbound/anytls.go @@ -6,7 +6,7 @@ import ( "strconv" "time" - CN "github.com/metacubex/mihomo/common/net" + N "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/component/proxydialer" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/transport/anytls" @@ -63,7 +63,7 @@ func (t *AnyTLS) ListenPacketContext(ctx context.Context, metadata *C.Metadata) // create uot on tcp destination := M.SocksaddrFromNet(metadata.UDPAddr()) - return newPacketConn(CN.NewThreadSafePacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination})), t), nil + return newPacketConn(N.NewThreadSafePacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination})), t), nil } // SupportUOT implements C.ProxyAdapter @@ -90,12 +90,13 @@ func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) { name: option.Name, addr: addr, tp: C.AnyTLS, + pdName: option.ProviderName, udp: option.UDP, tfo: option.TFO, mpTcp: option.MPTCP, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, option: &option, } diff --git a/clash-meta/adapter/outbound/base.go b/clash-meta/adapter/outbound/base.go index a3408f70fb..4a4c412d05 100644 --- a/clash-meta/adapter/outbound/base.go +++ b/clash-meta/adapter/outbound/base.go @@ -27,16 +27,17 @@ type ProxyAdapter interface { type Base struct { name string addr string - iface string tp C.AdapterType + pdName string udp bool xudp bool tfo bool mpTcp bool + iface string rmark int - id string prefer C.DNSPrefer dialer C.Dialer + id string } // Name implements C.ProxyAdapter @@ -85,6 +86,7 @@ func (b *Base) ProxyInfo() (info C.ProxyInfo) { info.SMUX = false info.Interface = b.iface info.RoutingMark = b.rmark + info.ProviderName = b.pdName return } @@ -160,14 +162,18 @@ func (b *Base) Close() error { } type BasicOption struct { - TFO bool `proxy:"tfo,omitempty"` - MPTCP bool `proxy:"mptcp,omitempty"` - Interface string `proxy:"interface-name,omitempty"` - RoutingMark int `proxy:"routing-mark,omitempty"` - IPVersion string `proxy:"ip-version,omitempty"` - DialerProxy string `proxy:"dialer-proxy,omitempty"` // don't apply this option into groups, but can set a group name in a proxy + TFO bool `proxy:"tfo,omitempty"` + MPTCP bool `proxy:"mptcp,omitempty"` + Interface string `proxy:"interface-name,omitempty"` + RoutingMark int `proxy:"routing-mark,omitempty"` + IPVersion C.DNSPrefer `proxy:"ip-version,omitempty"` + DialerProxy string `proxy:"dialer-proxy,omitempty"` // don't apply this option into groups, but can set a group name in a proxy + // + // The following parameters are used internally, assign value by the structure decoder are disallowed + // DialerForAPI C.Dialer `proxy:"-"` // the dialer used for API usage has higher priority than all the above configurations. + ProviderName string `proxy:"-"` } func (b *BasicOption) NewDialer(opts []dialer.Option) C.Dialer { @@ -213,6 +219,7 @@ func NewBase(opt BaseOption) *Base { type conn struct { N.ExtendedConn chain C.Chain + pdChain C.Chain adapterAddr string } @@ -234,9 +241,15 @@ func (c *conn) Chains() C.Chain { return c.chain } +// ProviderChains implements C.Connection +func (c *conn) ProviderChains() C.Chain { + return c.pdChain +} + // AppendToChains implements C.Connection func (c *conn) AppendToChains(a C.ProxyAdapter) { c.chain = append(c.chain, a.Name()) + c.pdChain = append(c.pdChain, a.ProxyInfo().ProviderName) } func (c *conn) Upstream() any { @@ -259,7 +272,7 @@ func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn { if _, ok := c.(syscall.Conn); !ok { // exclusion system conn like *net.TCPConn c = N.NewDeadlineConn(c) // most conn from outbound can't handle readDeadline correctly } - cc := &conn{N.NewExtendedConn(c), nil, a.Addr()} + cc := &conn{N.NewExtendedConn(c), nil, nil, a.Addr()} cc.AppendToChains(a) return cc } @@ -267,6 +280,7 @@ func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn { type packetConn struct { N.EnhancePacketConn chain C.Chain + pdChain C.Chain adapterName string connID string adapterAddr string @@ -287,9 +301,15 @@ func (c *packetConn) Chains() C.Chain { return c.chain } +// ProviderChains implements C.Connection +func (c *packetConn) ProviderChains() C.Chain { + return c.pdChain +} + // AppendToChains implements C.Connection func (c *packetConn) AppendToChains(a C.ProxyAdapter) { c.chain = append(c.chain, a.Name()) + c.pdChain = append(c.pdChain, a.ProxyInfo().ProviderName) } func (c *packetConn) LocalAddr() net.Addr { @@ -318,7 +338,7 @@ func newPacketConn(pc net.PacketConn, a ProxyAdapter) C.PacketConn { if _, ok := pc.(syscall.Conn); !ok { // exclusion system conn like *net.UDPConn epc = N.NewDeadlineEnhancePacketConn(epc) // most conn from outbound can't handle readDeadline correctly } - cpc := &packetConn{epc, nil, a.Name(), utils.NewUUIDV4().String(), a.Addr(), a.ResolveUDP} + cpc := &packetConn{epc, nil, nil, a.Name(), utils.NewUUIDV4().String(), a.Addr(), a.ResolveUDP} cpc.AppendToChains(a) return cpc } diff --git a/clash-meta/adapter/outbound/direct.go b/clash-meta/adapter/outbound/direct.go index be8367ba8f..42cd8def13 100644 --- a/clash-meta/adapter/outbound/direct.go +++ b/clash-meta/adapter/outbound/direct.go @@ -69,12 +69,13 @@ func NewDirectWithOption(option DirectOption) *Direct { Base: &Base{ name: option.Name, tp: C.Direct, + pdName: option.ProviderName, udp: true, tfo: option.TFO, mpTcp: option.MPTCP, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, loopBack: loopback.NewDetector(), } diff --git a/clash-meta/adapter/outbound/dns.go b/clash-meta/adapter/outbound/dns.go index 2522850247..5e253d2a01 100644 --- a/clash-meta/adapter/outbound/dns.go +++ b/clash-meta/adapter/outbound/dns.go @@ -158,12 +158,13 @@ func NewDnsWithOption(option DnsOption) *Dns { Base: &Base{ name: option.Name, tp: C.Dns, + pdName: option.ProviderName, udp: true, tfo: option.TFO, mpTcp: option.MPTCP, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, } } diff --git a/clash-meta/adapter/outbound/http.go b/clash-meta/adapter/outbound/http.go index 61b8596ad5..9707bf13c2 100644 --- a/clash-meta/adapter/outbound/http.go +++ b/clash-meta/adapter/outbound/http.go @@ -170,11 +170,12 @@ func NewHttp(option HttpOption) (*Http, error) { name: option.Name, addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), tp: C.Http, + pdName: option.ProviderName, tfo: option.TFO, mpTcp: option.MPTCP, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, user: option.UserName, pass: option.Password, diff --git a/clash-meta/adapter/outbound/hysteria.go b/clash-meta/adapter/outbound/hysteria.go index 4673779861..67c9af0b6e 100644 --- a/clash-meta/adapter/outbound/hysteria.go +++ b/clash-meta/adapter/outbound/hysteria.go @@ -243,11 +243,12 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) { name: option.Name, addr: addr, tp: C.Hysteria, + pdName: option.ProviderName, udp: true, tfo: option.FastOpen, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, option: &option, client: client, diff --git a/clash-meta/adapter/outbound/hysteria2.go b/clash-meta/adapter/outbound/hysteria2.go index 78d2b9ff95..2982276938 100644 --- a/clash-meta/adapter/outbound/hysteria2.go +++ b/clash-meta/adapter/outbound/hysteria2.go @@ -9,7 +9,7 @@ import ( "strconv" "time" - CN "github.com/metacubex/mihomo/common/net" + N "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/proxydialer" @@ -85,7 +85,7 @@ func (h *Hysteria2) ListenPacketContext(ctx context.Context, metadata *C.Metadat if pc == nil { return nil, errors.New("packetConn is nil") } - return newPacketConn(CN.NewThreadSafePacketConn(pc), h), nil + return newPacketConn(N.NewThreadSafePacketConn(pc), h), nil } // Close implements C.ProxyAdapter @@ -110,10 +110,11 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) { name: option.Name, addr: addr, tp: C.Hysteria2, + pdName: option.ProviderName, udp: true, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, option: &option, } @@ -189,7 +190,7 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) { CWND: option.CWND, UdpMTU: option.UdpMTU, ServerAddress: func(ctx context.Context) (*net.UDPAddr, error) { - udpAddr, err := resolveUDPAddr(ctx, "udp", addr, C.NewDNSPrefer(option.IPVersion)) + udpAddr, err := resolveUDPAddr(ctx, "udp", addr, option.IPVersion) if err != nil { return nil, err } diff --git a/clash-meta/adapter/outbound/mieru.go b/clash-meta/adapter/outbound/mieru.go index 5a2db6309b..af790b6e05 100644 --- a/clash-meta/adapter/outbound/mieru.go +++ b/clash-meta/adapter/outbound/mieru.go @@ -8,7 +8,7 @@ import ( "strconv" "sync" - CN "github.com/metacubex/mihomo/common/net" + N "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/component/resolver" C "github.com/metacubex/mihomo/constant" @@ -104,7 +104,7 @@ func (m *Mieru) ListenPacketContext(ctx context.Context, metadata *C.Metadata) ( if err != nil { return nil, fmt.Errorf("dial to %s failed: %w", metadata.UDPAddr(), err) } - return newPacketConn(CN.NewThreadSafePacketConn(mierucommon.NewUDPAssociateWrapper(mierucommon.NewPacketOverStreamTunnel(c))), m), nil + return newPacketConn(N.NewThreadSafePacketConn(mierucommon.NewUDPAssociateWrapper(mierucommon.NewPacketOverStreamTunnel(c))), m), nil } // SupportUOT implements C.ProxyAdapter @@ -167,12 +167,13 @@ func NewMieru(option MieruOption) (*Mieru, error) { Base: &Base{ name: option.Name, addr: addr, - iface: option.Interface, tp: C.Mieru, + pdName: option.ProviderName, udp: option.UDP, xudp: false, + iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, option: &option, client: c, diff --git a/clash-meta/adapter/outbound/shadowsocks.go b/clash-meta/adapter/outbound/shadowsocks.go index 0ece815125..8e4d9354ad 100644 --- a/clash-meta/adapter/outbound/shadowsocks.go +++ b/clash-meta/adapter/outbound/shadowsocks.go @@ -458,12 +458,13 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { name: option.Name, addr: addr, tp: C.Shadowsocks, + pdName: option.ProviderName, udp: option.UDP, tfo: option.TFO, mpTcp: option.MPTCP, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, method: method, diff --git a/clash-meta/adapter/outbound/shadowsocksr.go b/clash-meta/adapter/outbound/shadowsocksr.go index 8eecf677e3..09c3924e27 100644 --- a/clash-meta/adapter/outbound/shadowsocksr.go +++ b/clash-meta/adapter/outbound/shadowsocksr.go @@ -162,12 +162,13 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) { name: option.Name, addr: addr, tp: C.ShadowsocksR, + pdName: option.ProviderName, udp: option.UDP, tfo: option.TFO, mpTcp: option.MPTCP, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, option: &option, cipher: coreCiph, diff --git a/clash-meta/adapter/outbound/singmux.go b/clash-meta/adapter/outbound/singmux.go index 310e3b61e1..55bd6ac385 100644 --- a/clash-meta/adapter/outbound/singmux.go +++ b/clash-meta/adapter/outbound/singmux.go @@ -3,7 +3,7 @@ package outbound import ( "context" - CN "github.com/metacubex/mihomo/common/net" + N "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/component/proxydialer" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/log" @@ -59,7 +59,7 @@ func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata) if pc == nil { return nil, E.New("packetConn is nil") } - return newPacketConn(CN.NewThreadSafePacketConn(pc), s), nil + return newPacketConn(N.NewThreadSafePacketConn(pc), s), nil } func (s *SingMux) SupportUDP() bool { diff --git a/clash-meta/adapter/outbound/snell.go b/clash-meta/adapter/outbound/snell.go index 0c7d8876ee..64295a7083 100644 --- a/clash-meta/adapter/outbound/snell.go +++ b/clash-meta/adapter/outbound/snell.go @@ -165,12 +165,13 @@ func NewSnell(option SnellOption) (*Snell, error) { name: option.Name, addr: addr, tp: C.Snell, + pdName: option.ProviderName, udp: option.UDP, tfo: option.TFO, mpTcp: option.MPTCP, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, option: &option, psk: psk, diff --git a/clash-meta/adapter/outbound/socks5.go b/clash-meta/adapter/outbound/socks5.go index 3bc48d8626..c00480844a 100644 --- a/clash-meta/adapter/outbound/socks5.go +++ b/clash-meta/adapter/outbound/socks5.go @@ -190,12 +190,13 @@ func NewSocks5(option Socks5Option) (*Socks5, error) { name: option.Name, addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), tp: C.Socks5, + pdName: option.ProviderName, udp: option.UDP, tfo: option.TFO, mpTcp: option.MPTCP, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, option: &option, user: option.UserName, diff --git a/clash-meta/adapter/outbound/ssh.go b/clash-meta/adapter/outbound/ssh.go index ec9068dd9b..5dca2b1720 100644 --- a/clash-meta/adapter/outbound/ssh.go +++ b/clash-meta/adapter/outbound/ssh.go @@ -186,10 +186,11 @@ func NewSsh(option SshOption) (*Ssh, error) { name: option.Name, addr: addr, tp: C.Ssh, + pdName: option.ProviderName, udp: false, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, option: &option, config: &config, diff --git a/clash-meta/adapter/outbound/sudoku.go b/clash-meta/adapter/outbound/sudoku.go index 0e6c3bba68..44affacccf 100644 --- a/clash-meta/adapter/outbound/sudoku.go +++ b/clash-meta/adapter/outbound/sudoku.go @@ -238,12 +238,13 @@ func NewSudoku(option SudokuOption) (*Sudoku, error) { name: option.Name, addr: baseConf.ServerAddress, tp: C.Sudoku, + pdName: option.ProviderName, udp: true, tfo: option.TFO, mpTcp: option.MPTCP, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, option: &option, table: table, diff --git a/clash-meta/adapter/outbound/trojan.go b/clash-meta/adapter/outbound/trojan.go index d2f7f38afb..cf86f7b452 100644 --- a/clash-meta/adapter/outbound/trojan.go +++ b/clash-meta/adapter/outbound/trojan.go @@ -288,12 +288,13 @@ func NewTrojan(option TrojanOption) (*Trojan, error) { name: option.Name, addr: addr, tp: C.Trojan, + pdName: option.ProviderName, udp: option.UDP, tfo: option.TFO, mpTcp: option.MPTCP, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, option: &option, hexPassword: trojan.Key(option.Password), diff --git a/clash-meta/adapter/outbound/tuic.go b/clash-meta/adapter/outbound/tuic.go index 8070359ed8..e29ad3ccbc 100644 --- a/clash-meta/adapter/outbound/tuic.go +++ b/clash-meta/adapter/outbound/tuic.go @@ -252,11 +252,12 @@ func NewTuic(option TuicOption) (*Tuic, error) { name: option.Name, addr: addr, tp: C.Tuic, + pdName: option.ProviderName, udp: true, tfo: option.FastOpen, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, option: &option, tlsConfig: tlsClientConfig, diff --git a/clash-meta/adapter/outbound/vless.go b/clash-meta/adapter/outbound/vless.go index d630107693..c5c759ac90 100644 --- a/clash-meta/adapter/outbound/vless.go +++ b/clash-meta/adapter/outbound/vless.go @@ -417,13 +417,14 @@ func NewVless(option VlessOption) (*Vless, error) { name: option.Name, addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), tp: C.Vless, + pdName: option.ProviderName, udp: option.UDP, xudp: option.XUDP, tfo: option.TFO, mpTcp: option.MPTCP, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, client: client, option: &option, diff --git a/clash-meta/adapter/outbound/vmess.go b/clash-meta/adapter/outbound/vmess.go index 7af32c6db2..ea874250ff 100644 --- a/clash-meta/adapter/outbound/vmess.go +++ b/clash-meta/adapter/outbound/vmess.go @@ -427,13 +427,14 @@ func NewVmess(option VmessOption) (*Vmess, error) { name: option.Name, addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), tp: C.Vmess, + pdName: option.ProviderName, udp: option.UDP, xudp: option.XUDP, tfo: option.TFO, mpTcp: option.MPTCP, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, client: client, option: &option, diff --git a/clash-meta/adapter/outbound/wireguard.go b/clash-meta/adapter/outbound/wireguard.go index 66d46b0219..86f8a61bbf 100644 --- a/clash-meta/adapter/outbound/wireguard.go +++ b/clash-meta/adapter/outbound/wireguard.go @@ -170,10 +170,11 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) { name: option.Name, addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), tp: C.WireGuard, + pdName: option.ProviderName, udp: option.UDP, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, } outbound.dialer = option.NewDialer(outbound.DialOptions()) diff --git a/clash-meta/adapter/parser.go b/clash-meta/adapter/parser.go index 0ad45ca956..08f90afe34 100644 --- a/clash-meta/adapter/parser.go +++ b/clash-meta/adapter/parser.go @@ -18,6 +18,7 @@ func ParseProxy(mapping map[string]any, options ...ProxyOption) (C.Proxy, error) opt := applyProxyOptions(options...) basicOption := outbound.BasicOption{ DialerForAPI: opt.DialerForAPI, + ProviderName: opt.ProviderName, } var ( @@ -186,6 +187,7 @@ func ParseProxy(mapping map[string]any, options ...ProxyOption) (C.Proxy, error) type proxyOption struct { DialerForAPI C.Dialer + ProviderName string } func applyProxyOptions(options ...ProxyOption) proxyOption { @@ -203,3 +205,9 @@ func WithDialerForAPI(dialer C.Dialer) ProxyOption { opt.DialerForAPI = dialer } } + +func WithProviderName(name string) ProxyOption { + return func(opt *proxyOption) { + opt.ProviderName = name + } +} diff --git a/clash-meta/adapter/provider/parser.go b/clash-meta/adapter/provider/parser.go index d6297b5662..6e0da67837 100644 --- a/clash-meta/adapter/provider/parser.go +++ b/clash-meta/adapter/provider/parser.go @@ -99,7 +99,7 @@ func ParseProxyProvider(name string, mapping map[string]any) (P.ProxyProvider, e } hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, uint(schema.HealthCheck.TestTimeout), hcInterval, schema.HealthCheck.Lazy, expectedStatus) - parser, err := NewProxiesParser(schema.Filter, schema.ExcludeFilter, schema.ExcludeType, schema.DialerProxy, schema.Override) + parser, err := NewProxiesParser(name, schema.Filter, schema.ExcludeFilter, schema.ExcludeType, schema.DialerProxy, schema.Override) if err != nil { return nil, err } diff --git a/clash-meta/adapter/provider/provider.go b/clash-meta/adapter/provider/provider.go index e5d3b8acd5..71713d8fbf 100644 --- a/clash-meta/adapter/provider/provider.go +++ b/clash-meta/adapter/provider/provider.go @@ -156,7 +156,7 @@ func (pp *proxySetProvider) Initial() error { func (pp *proxySetProvider) closeAllConnections() { statistic.DefaultManager.Range(func(c statistic.Tracker) bool { - for _, chain := range c.Chains() { + for _, chain := range c.ProviderChains() { if chain == pp.Name() { _ = c.Close() break @@ -330,7 +330,7 @@ func (cp *CompatibleProvider) Close() error { return cp.compatibleProvider.Close() } -func NewProxiesParser(filter string, excludeFilter string, excludeType string, dialerProxy string, override OverrideSchema) (resource.Parser[[]C.Proxy], error) { +func NewProxiesParser(pdName string, filter string, excludeFilter string, excludeType string, dialerProxy string, override OverrideSchema) (resource.Parser[[]C.Proxy], error) { var excludeTypeArray []string if excludeType != "" { excludeTypeArray = strings.Split(excludeType, "|") @@ -448,7 +448,7 @@ func NewProxiesParser(filter string, excludeFilter string, excludeType string, d } } - proxy, err := adapter.ParseProxy(mapping) + proxy, err := adapter.ParseProxy(mapping, adapter.WithProviderName(pdName)) if err != nil { return nil, fmt.Errorf("proxy %d error: %w", idx, err) } diff --git a/clash-meta/common/structure/structure.go b/clash-meta/common/structure/structure.go index 840e4f6cad..d43dec033e 100644 --- a/clash-meta/common/structure/structure.go +++ b/clash-meta/common/structure/structure.go @@ -517,6 +517,10 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e fieldName = tagValue } + if tagValue == "-" { + continue + } + rawMapKey := reflect.ValueOf(fieldName) rawMapVal := dataVal.MapIndex(rawMapKey) if !rawMapVal.IsValid() { diff --git a/clash-meta/common/structure/structure_test.go b/clash-meta/common/structure/structure_test.go index 06dffd0f32..c79c2eb46f 100644 --- a/clash-meta/common/structure/structure_test.go +++ b/clash-meta/common/structure/structure_test.go @@ -308,3 +308,27 @@ func TestStructure_Ignore(t *testing.T) { assert.Nil(t, err) assert.Equal(t, s.MustIgnore, "oldData") } + +func TestStructure_IgnoreInNest(t *testing.T) { + rawMap := map[string]any{ + "-": "newData", + } + + type TP struct { + MustIgnore string `test:"-"` + } + + s := struct { + TP + }{TP{MustIgnore: "oldData"}} + + err := decoder.Decode(rawMap, &s) + assert.Nil(t, err) + assert.Equal(t, s.MustIgnore, "oldData") + + // test omitempty + delete(rawMap, "-") + err = decoder.Decode(rawMap, &s) + assert.Nil(t, err) + assert.Equal(t, s.MustIgnore, "oldData") +} diff --git a/clash-meta/constant/adapters.go b/clash-meta/constant/adapters.go index 0b9098fd69..07ae5de1d8 100644 --- a/clash-meta/constant/adapters.go +++ b/clash-meta/constant/adapters.go @@ -59,6 +59,7 @@ var ErrNotSupport = errors.New("no support") type Connection interface { Chains() Chain + ProviderChains() Chain AppendToChains(adapter ProxyAdapter) RemoteDestination() string } @@ -102,13 +103,14 @@ type Dialer interface { } type ProxyInfo struct { - XUDP bool - TFO bool - MPTCP bool - SMUX bool - Interface string - RoutingMark int - DialerProxy string + XUDP bool + TFO bool + MPTCP bool + SMUX bool + Interface string + RoutingMark int + ProviderName string + DialerProxy string } type ProxyAdapter interface { diff --git a/clash-meta/constant/dns.go b/clash-meta/constant/dns.go index 13a2ed362a..79dafd2bb9 100644 --- a/clash-meta/constant/dns.go +++ b/clash-meta/constant/dns.go @@ -86,12 +86,17 @@ func (d DNSPrefer) String() string { } } -func NewDNSPrefer(prefer string) DNSPrefer { - if p, ok := dnsPreferMap[prefer]; ok { - return p - } else { - return DualStack +func (d DNSPrefer) MarshalText() ([]byte, error) { + return []byte(d.String()), nil +} + +func (d *DNSPrefer) UnmarshalText(data []byte) error { + p, exist := dnsPreferMap[strings.ToLower(string(data))] + if !exist { + p = DualStack } + *d = p + return nil } // FilterModeMapping is a mapping for FilterMode enum diff --git a/clash-meta/tunnel/statistic/tracker.go b/clash-meta/tunnel/statistic/tracker.go index ffa2e71ede..2fbb1cf9e3 100644 --- a/clash-meta/tunnel/statistic/tracker.go +++ b/clash-meta/tunnel/statistic/tracker.go @@ -28,6 +28,7 @@ type TrackerInfo struct { DownloadTotal atomic.Int64 `json:"download"` Start time.Time `json:"start"` Chain C.Chain `json:"chains"` + ProviderChain C.Chain `json:"providerChains"` Rule string `json:"rule"` RulePayload string `json:"rulePayload"` } @@ -126,6 +127,7 @@ func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.R Start: time.Now(), Metadata: metadata, Chain: conn.Chains(), + ProviderChain: conn.ProviderChains(), Rule: "", UploadTotal: atomic.NewInt64(uploadTotal), DownloadTotal: atomic.NewInt64(downloadTotal), @@ -217,6 +219,7 @@ func NewUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, ru Start: time.Now(), Metadata: metadata, Chain: conn.Chains(), + ProviderChain: conn.ProviderChains(), Rule: "", UploadTotal: atomic.NewInt64(uploadTotal), DownloadTotal: atomic.NewInt64(downloadTotal), diff --git a/clash-nyanpasu/manifest/version.json b/clash-nyanpasu/manifest/version.json index 6a5b776520..597c367441 100644 --- a/clash-nyanpasu/manifest/version.json +++ b/clash-nyanpasu/manifest/version.json @@ -2,7 +2,7 @@ "manifest_version": 1, "latest": { "mihomo": "v1.19.17", - "mihomo_alpha": "alpha-fdb7cb1", + "mihomo_alpha": "alpha-32ce513", "clash_rs": "v0.9.2", "clash_premium": "2023-09-05-gdcc8d87", "clash_rs_alpha": "0.9.2-alpha+sha.e1f8fbb" @@ -69,5 +69,5 @@ "linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf" } }, - "updated_at": "2025-12-02T22:21:02.066Z" + "updated_at": "2025-12-03T22:21:43.313Z" } diff --git a/lede/include/target.mk b/lede/include/target.mk index 12e17098ec..de9f9a63e1 100644 --- a/lede/include/target.mk +++ b/lede/include/target.mk @@ -299,6 +299,9 @@ ifeq ($(DUMP),1) ifneq ($(CONFIG_PCIEPORTBUS),) FEATURES += pcie endif + ifneq ($(CONFIG_PWM),) + FEATURES += pwm + endif ifneq ($(CONFIG_USB)$(CONFIG_USB_SUPPORT),) ifneq ($(CONFIG_USB_ARCH_HAS_HCD)$(CONFIG_USB_EHCI_HCD),) FEATURES += usb diff --git a/mihomo/adapter/adapter.go b/mihomo/adapter/adapter.go index ef8d4ee347..815064568b 100644 --- a/mihomo/adapter/adapter.go +++ b/mihomo/adapter/adapter.go @@ -153,8 +153,9 @@ func (p *Proxy) MarshalJSON() ([]byte, error) { mapping["mptcp"] = proxyInfo.MPTCP mapping["smux"] = proxyInfo.SMUX mapping["interface"] = proxyInfo.Interface - mapping["dialer-proxy"] = proxyInfo.DialerProxy mapping["routing-mark"] = proxyInfo.RoutingMark + mapping["provider-name"] = proxyInfo.ProviderName + mapping["dialer-proxy"] = proxyInfo.DialerProxy return json.Marshal(mapping) } @@ -177,14 +178,12 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In p.history.Pop() } - state, ok := p.extra.Load(url) - if !ok { - state = &internalProxyState{ + state, _ := p.extra.LoadOrStoreFn(url, func() *internalProxyState { + return &internalProxyState{ history: queue.New[C.DelayHistory](defaultHistoriesNum), alive: atomic.NewBool(true), } - p.extra.Store(url, state) - } + }) if !satisfied { record.Delay = 0 diff --git a/mihomo/adapter/outbound/anytls.go b/mihomo/adapter/outbound/anytls.go index 5ba7c6d265..c17f27d55e 100644 --- a/mihomo/adapter/outbound/anytls.go +++ b/mihomo/adapter/outbound/anytls.go @@ -6,7 +6,7 @@ import ( "strconv" "time" - CN "github.com/metacubex/mihomo/common/net" + N "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/component/proxydialer" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/transport/anytls" @@ -63,7 +63,7 @@ func (t *AnyTLS) ListenPacketContext(ctx context.Context, metadata *C.Metadata) // create uot on tcp destination := M.SocksaddrFromNet(metadata.UDPAddr()) - return newPacketConn(CN.NewThreadSafePacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination})), t), nil + return newPacketConn(N.NewThreadSafePacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination})), t), nil } // SupportUOT implements C.ProxyAdapter @@ -90,12 +90,13 @@ func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) { name: option.Name, addr: addr, tp: C.AnyTLS, + pdName: option.ProviderName, udp: option.UDP, tfo: option.TFO, mpTcp: option.MPTCP, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, option: &option, } diff --git a/mihomo/adapter/outbound/base.go b/mihomo/adapter/outbound/base.go index a3408f70fb..4a4c412d05 100644 --- a/mihomo/adapter/outbound/base.go +++ b/mihomo/adapter/outbound/base.go @@ -27,16 +27,17 @@ type ProxyAdapter interface { type Base struct { name string addr string - iface string tp C.AdapterType + pdName string udp bool xudp bool tfo bool mpTcp bool + iface string rmark int - id string prefer C.DNSPrefer dialer C.Dialer + id string } // Name implements C.ProxyAdapter @@ -85,6 +86,7 @@ func (b *Base) ProxyInfo() (info C.ProxyInfo) { info.SMUX = false info.Interface = b.iface info.RoutingMark = b.rmark + info.ProviderName = b.pdName return } @@ -160,14 +162,18 @@ func (b *Base) Close() error { } type BasicOption struct { - TFO bool `proxy:"tfo,omitempty"` - MPTCP bool `proxy:"mptcp,omitempty"` - Interface string `proxy:"interface-name,omitempty"` - RoutingMark int `proxy:"routing-mark,omitempty"` - IPVersion string `proxy:"ip-version,omitempty"` - DialerProxy string `proxy:"dialer-proxy,omitempty"` // don't apply this option into groups, but can set a group name in a proxy + TFO bool `proxy:"tfo,omitempty"` + MPTCP bool `proxy:"mptcp,omitempty"` + Interface string `proxy:"interface-name,omitempty"` + RoutingMark int `proxy:"routing-mark,omitempty"` + IPVersion C.DNSPrefer `proxy:"ip-version,omitempty"` + DialerProxy string `proxy:"dialer-proxy,omitempty"` // don't apply this option into groups, but can set a group name in a proxy + // + // The following parameters are used internally, assign value by the structure decoder are disallowed + // DialerForAPI C.Dialer `proxy:"-"` // the dialer used for API usage has higher priority than all the above configurations. + ProviderName string `proxy:"-"` } func (b *BasicOption) NewDialer(opts []dialer.Option) C.Dialer { @@ -213,6 +219,7 @@ func NewBase(opt BaseOption) *Base { type conn struct { N.ExtendedConn chain C.Chain + pdChain C.Chain adapterAddr string } @@ -234,9 +241,15 @@ func (c *conn) Chains() C.Chain { return c.chain } +// ProviderChains implements C.Connection +func (c *conn) ProviderChains() C.Chain { + return c.pdChain +} + // AppendToChains implements C.Connection func (c *conn) AppendToChains(a C.ProxyAdapter) { c.chain = append(c.chain, a.Name()) + c.pdChain = append(c.pdChain, a.ProxyInfo().ProviderName) } func (c *conn) Upstream() any { @@ -259,7 +272,7 @@ func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn { if _, ok := c.(syscall.Conn); !ok { // exclusion system conn like *net.TCPConn c = N.NewDeadlineConn(c) // most conn from outbound can't handle readDeadline correctly } - cc := &conn{N.NewExtendedConn(c), nil, a.Addr()} + cc := &conn{N.NewExtendedConn(c), nil, nil, a.Addr()} cc.AppendToChains(a) return cc } @@ -267,6 +280,7 @@ func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn { type packetConn struct { N.EnhancePacketConn chain C.Chain + pdChain C.Chain adapterName string connID string adapterAddr string @@ -287,9 +301,15 @@ func (c *packetConn) Chains() C.Chain { return c.chain } +// ProviderChains implements C.Connection +func (c *packetConn) ProviderChains() C.Chain { + return c.pdChain +} + // AppendToChains implements C.Connection func (c *packetConn) AppendToChains(a C.ProxyAdapter) { c.chain = append(c.chain, a.Name()) + c.pdChain = append(c.pdChain, a.ProxyInfo().ProviderName) } func (c *packetConn) LocalAddr() net.Addr { @@ -318,7 +338,7 @@ func newPacketConn(pc net.PacketConn, a ProxyAdapter) C.PacketConn { if _, ok := pc.(syscall.Conn); !ok { // exclusion system conn like *net.UDPConn epc = N.NewDeadlineEnhancePacketConn(epc) // most conn from outbound can't handle readDeadline correctly } - cpc := &packetConn{epc, nil, a.Name(), utils.NewUUIDV4().String(), a.Addr(), a.ResolveUDP} + cpc := &packetConn{epc, nil, nil, a.Name(), utils.NewUUIDV4().String(), a.Addr(), a.ResolveUDP} cpc.AppendToChains(a) return cpc } diff --git a/mihomo/adapter/outbound/direct.go b/mihomo/adapter/outbound/direct.go index be8367ba8f..42cd8def13 100644 --- a/mihomo/adapter/outbound/direct.go +++ b/mihomo/adapter/outbound/direct.go @@ -69,12 +69,13 @@ func NewDirectWithOption(option DirectOption) *Direct { Base: &Base{ name: option.Name, tp: C.Direct, + pdName: option.ProviderName, udp: true, tfo: option.TFO, mpTcp: option.MPTCP, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, loopBack: loopback.NewDetector(), } diff --git a/mihomo/adapter/outbound/dns.go b/mihomo/adapter/outbound/dns.go index 2522850247..5e253d2a01 100644 --- a/mihomo/adapter/outbound/dns.go +++ b/mihomo/adapter/outbound/dns.go @@ -158,12 +158,13 @@ func NewDnsWithOption(option DnsOption) *Dns { Base: &Base{ name: option.Name, tp: C.Dns, + pdName: option.ProviderName, udp: true, tfo: option.TFO, mpTcp: option.MPTCP, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, } } diff --git a/mihomo/adapter/outbound/http.go b/mihomo/adapter/outbound/http.go index 61b8596ad5..9707bf13c2 100644 --- a/mihomo/adapter/outbound/http.go +++ b/mihomo/adapter/outbound/http.go @@ -170,11 +170,12 @@ func NewHttp(option HttpOption) (*Http, error) { name: option.Name, addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), tp: C.Http, + pdName: option.ProviderName, tfo: option.TFO, mpTcp: option.MPTCP, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, user: option.UserName, pass: option.Password, diff --git a/mihomo/adapter/outbound/hysteria.go b/mihomo/adapter/outbound/hysteria.go index 4673779861..67c9af0b6e 100644 --- a/mihomo/adapter/outbound/hysteria.go +++ b/mihomo/adapter/outbound/hysteria.go @@ -243,11 +243,12 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) { name: option.Name, addr: addr, tp: C.Hysteria, + pdName: option.ProviderName, udp: true, tfo: option.FastOpen, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, option: &option, client: client, diff --git a/mihomo/adapter/outbound/hysteria2.go b/mihomo/adapter/outbound/hysteria2.go index 78d2b9ff95..2982276938 100644 --- a/mihomo/adapter/outbound/hysteria2.go +++ b/mihomo/adapter/outbound/hysteria2.go @@ -9,7 +9,7 @@ import ( "strconv" "time" - CN "github.com/metacubex/mihomo/common/net" + N "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/proxydialer" @@ -85,7 +85,7 @@ func (h *Hysteria2) ListenPacketContext(ctx context.Context, metadata *C.Metadat if pc == nil { return nil, errors.New("packetConn is nil") } - return newPacketConn(CN.NewThreadSafePacketConn(pc), h), nil + return newPacketConn(N.NewThreadSafePacketConn(pc), h), nil } // Close implements C.ProxyAdapter @@ -110,10 +110,11 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) { name: option.Name, addr: addr, tp: C.Hysteria2, + pdName: option.ProviderName, udp: true, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, option: &option, } @@ -189,7 +190,7 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) { CWND: option.CWND, UdpMTU: option.UdpMTU, ServerAddress: func(ctx context.Context) (*net.UDPAddr, error) { - udpAddr, err := resolveUDPAddr(ctx, "udp", addr, C.NewDNSPrefer(option.IPVersion)) + udpAddr, err := resolveUDPAddr(ctx, "udp", addr, option.IPVersion) if err != nil { return nil, err } diff --git a/mihomo/adapter/outbound/mieru.go b/mihomo/adapter/outbound/mieru.go index 5a2db6309b..af790b6e05 100644 --- a/mihomo/adapter/outbound/mieru.go +++ b/mihomo/adapter/outbound/mieru.go @@ -8,7 +8,7 @@ import ( "strconv" "sync" - CN "github.com/metacubex/mihomo/common/net" + N "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/component/resolver" C "github.com/metacubex/mihomo/constant" @@ -104,7 +104,7 @@ func (m *Mieru) ListenPacketContext(ctx context.Context, metadata *C.Metadata) ( if err != nil { return nil, fmt.Errorf("dial to %s failed: %w", metadata.UDPAddr(), err) } - return newPacketConn(CN.NewThreadSafePacketConn(mierucommon.NewUDPAssociateWrapper(mierucommon.NewPacketOverStreamTunnel(c))), m), nil + return newPacketConn(N.NewThreadSafePacketConn(mierucommon.NewUDPAssociateWrapper(mierucommon.NewPacketOverStreamTunnel(c))), m), nil } // SupportUOT implements C.ProxyAdapter @@ -167,12 +167,13 @@ func NewMieru(option MieruOption) (*Mieru, error) { Base: &Base{ name: option.Name, addr: addr, - iface: option.Interface, tp: C.Mieru, + pdName: option.ProviderName, udp: option.UDP, xudp: false, + iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, option: &option, client: c, diff --git a/mihomo/adapter/outbound/shadowsocks.go b/mihomo/adapter/outbound/shadowsocks.go index 0ece815125..8e4d9354ad 100644 --- a/mihomo/adapter/outbound/shadowsocks.go +++ b/mihomo/adapter/outbound/shadowsocks.go @@ -458,12 +458,13 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { name: option.Name, addr: addr, tp: C.Shadowsocks, + pdName: option.ProviderName, udp: option.UDP, tfo: option.TFO, mpTcp: option.MPTCP, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, method: method, diff --git a/mihomo/adapter/outbound/shadowsocksr.go b/mihomo/adapter/outbound/shadowsocksr.go index 8eecf677e3..09c3924e27 100644 --- a/mihomo/adapter/outbound/shadowsocksr.go +++ b/mihomo/adapter/outbound/shadowsocksr.go @@ -162,12 +162,13 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) { name: option.Name, addr: addr, tp: C.ShadowsocksR, + pdName: option.ProviderName, udp: option.UDP, tfo: option.TFO, mpTcp: option.MPTCP, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, option: &option, cipher: coreCiph, diff --git a/mihomo/adapter/outbound/singmux.go b/mihomo/adapter/outbound/singmux.go index 310e3b61e1..55bd6ac385 100644 --- a/mihomo/adapter/outbound/singmux.go +++ b/mihomo/adapter/outbound/singmux.go @@ -3,7 +3,7 @@ package outbound import ( "context" - CN "github.com/metacubex/mihomo/common/net" + N "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/component/proxydialer" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/log" @@ -59,7 +59,7 @@ func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata) if pc == nil { return nil, E.New("packetConn is nil") } - return newPacketConn(CN.NewThreadSafePacketConn(pc), s), nil + return newPacketConn(N.NewThreadSafePacketConn(pc), s), nil } func (s *SingMux) SupportUDP() bool { diff --git a/mihomo/adapter/outbound/snell.go b/mihomo/adapter/outbound/snell.go index 0c7d8876ee..64295a7083 100644 --- a/mihomo/adapter/outbound/snell.go +++ b/mihomo/adapter/outbound/snell.go @@ -165,12 +165,13 @@ func NewSnell(option SnellOption) (*Snell, error) { name: option.Name, addr: addr, tp: C.Snell, + pdName: option.ProviderName, udp: option.UDP, tfo: option.TFO, mpTcp: option.MPTCP, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, option: &option, psk: psk, diff --git a/mihomo/adapter/outbound/socks5.go b/mihomo/adapter/outbound/socks5.go index 3bc48d8626..c00480844a 100644 --- a/mihomo/adapter/outbound/socks5.go +++ b/mihomo/adapter/outbound/socks5.go @@ -190,12 +190,13 @@ func NewSocks5(option Socks5Option) (*Socks5, error) { name: option.Name, addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), tp: C.Socks5, + pdName: option.ProviderName, udp: option.UDP, tfo: option.TFO, mpTcp: option.MPTCP, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, option: &option, user: option.UserName, diff --git a/mihomo/adapter/outbound/ssh.go b/mihomo/adapter/outbound/ssh.go index ec9068dd9b..5dca2b1720 100644 --- a/mihomo/adapter/outbound/ssh.go +++ b/mihomo/adapter/outbound/ssh.go @@ -186,10 +186,11 @@ func NewSsh(option SshOption) (*Ssh, error) { name: option.Name, addr: addr, tp: C.Ssh, + pdName: option.ProviderName, udp: false, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, option: &option, config: &config, diff --git a/mihomo/adapter/outbound/sudoku.go b/mihomo/adapter/outbound/sudoku.go index 0e6c3bba68..44affacccf 100644 --- a/mihomo/adapter/outbound/sudoku.go +++ b/mihomo/adapter/outbound/sudoku.go @@ -238,12 +238,13 @@ func NewSudoku(option SudokuOption) (*Sudoku, error) { name: option.Name, addr: baseConf.ServerAddress, tp: C.Sudoku, + pdName: option.ProviderName, udp: true, tfo: option.TFO, mpTcp: option.MPTCP, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, option: &option, table: table, diff --git a/mihomo/adapter/outbound/trojan.go b/mihomo/adapter/outbound/trojan.go index d2f7f38afb..cf86f7b452 100644 --- a/mihomo/adapter/outbound/trojan.go +++ b/mihomo/adapter/outbound/trojan.go @@ -288,12 +288,13 @@ func NewTrojan(option TrojanOption) (*Trojan, error) { name: option.Name, addr: addr, tp: C.Trojan, + pdName: option.ProviderName, udp: option.UDP, tfo: option.TFO, mpTcp: option.MPTCP, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, option: &option, hexPassword: trojan.Key(option.Password), diff --git a/mihomo/adapter/outbound/tuic.go b/mihomo/adapter/outbound/tuic.go index 8070359ed8..e29ad3ccbc 100644 --- a/mihomo/adapter/outbound/tuic.go +++ b/mihomo/adapter/outbound/tuic.go @@ -252,11 +252,12 @@ func NewTuic(option TuicOption) (*Tuic, error) { name: option.Name, addr: addr, tp: C.Tuic, + pdName: option.ProviderName, udp: true, tfo: option.FastOpen, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, option: &option, tlsConfig: tlsClientConfig, diff --git a/mihomo/adapter/outbound/vless.go b/mihomo/adapter/outbound/vless.go index d630107693..c5c759ac90 100644 --- a/mihomo/adapter/outbound/vless.go +++ b/mihomo/adapter/outbound/vless.go @@ -417,13 +417,14 @@ func NewVless(option VlessOption) (*Vless, error) { name: option.Name, addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), tp: C.Vless, + pdName: option.ProviderName, udp: option.UDP, xudp: option.XUDP, tfo: option.TFO, mpTcp: option.MPTCP, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, client: client, option: &option, diff --git a/mihomo/adapter/outbound/vmess.go b/mihomo/adapter/outbound/vmess.go index 7af32c6db2..ea874250ff 100644 --- a/mihomo/adapter/outbound/vmess.go +++ b/mihomo/adapter/outbound/vmess.go @@ -427,13 +427,14 @@ func NewVmess(option VmessOption) (*Vmess, error) { name: option.Name, addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), tp: C.Vmess, + pdName: option.ProviderName, udp: option.UDP, xudp: option.XUDP, tfo: option.TFO, mpTcp: option.MPTCP, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, client: client, option: &option, diff --git a/mihomo/adapter/outbound/wireguard.go b/mihomo/adapter/outbound/wireguard.go index 66d46b0219..86f8a61bbf 100644 --- a/mihomo/adapter/outbound/wireguard.go +++ b/mihomo/adapter/outbound/wireguard.go @@ -170,10 +170,11 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) { name: option.Name, addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), tp: C.WireGuard, + pdName: option.ProviderName, udp: option.UDP, iface: option.Interface, rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), + prefer: option.IPVersion, }, } outbound.dialer = option.NewDialer(outbound.DialOptions()) diff --git a/mihomo/adapter/parser.go b/mihomo/adapter/parser.go index 0ad45ca956..08f90afe34 100644 --- a/mihomo/adapter/parser.go +++ b/mihomo/adapter/parser.go @@ -18,6 +18,7 @@ func ParseProxy(mapping map[string]any, options ...ProxyOption) (C.Proxy, error) opt := applyProxyOptions(options...) basicOption := outbound.BasicOption{ DialerForAPI: opt.DialerForAPI, + ProviderName: opt.ProviderName, } var ( @@ -186,6 +187,7 @@ func ParseProxy(mapping map[string]any, options ...ProxyOption) (C.Proxy, error) type proxyOption struct { DialerForAPI C.Dialer + ProviderName string } func applyProxyOptions(options ...ProxyOption) proxyOption { @@ -203,3 +205,9 @@ func WithDialerForAPI(dialer C.Dialer) ProxyOption { opt.DialerForAPI = dialer } } + +func WithProviderName(name string) ProxyOption { + return func(opt *proxyOption) { + opt.ProviderName = name + } +} diff --git a/mihomo/adapter/provider/parser.go b/mihomo/adapter/provider/parser.go index d6297b5662..6e0da67837 100644 --- a/mihomo/adapter/provider/parser.go +++ b/mihomo/adapter/provider/parser.go @@ -99,7 +99,7 @@ func ParseProxyProvider(name string, mapping map[string]any) (P.ProxyProvider, e } hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, uint(schema.HealthCheck.TestTimeout), hcInterval, schema.HealthCheck.Lazy, expectedStatus) - parser, err := NewProxiesParser(schema.Filter, schema.ExcludeFilter, schema.ExcludeType, schema.DialerProxy, schema.Override) + parser, err := NewProxiesParser(name, schema.Filter, schema.ExcludeFilter, schema.ExcludeType, schema.DialerProxy, schema.Override) if err != nil { return nil, err } diff --git a/mihomo/adapter/provider/provider.go b/mihomo/adapter/provider/provider.go index e5d3b8acd5..71713d8fbf 100644 --- a/mihomo/adapter/provider/provider.go +++ b/mihomo/adapter/provider/provider.go @@ -156,7 +156,7 @@ func (pp *proxySetProvider) Initial() error { func (pp *proxySetProvider) closeAllConnections() { statistic.DefaultManager.Range(func(c statistic.Tracker) bool { - for _, chain := range c.Chains() { + for _, chain := range c.ProviderChains() { if chain == pp.Name() { _ = c.Close() break @@ -330,7 +330,7 @@ func (cp *CompatibleProvider) Close() error { return cp.compatibleProvider.Close() } -func NewProxiesParser(filter string, excludeFilter string, excludeType string, dialerProxy string, override OverrideSchema) (resource.Parser[[]C.Proxy], error) { +func NewProxiesParser(pdName string, filter string, excludeFilter string, excludeType string, dialerProxy string, override OverrideSchema) (resource.Parser[[]C.Proxy], error) { var excludeTypeArray []string if excludeType != "" { excludeTypeArray = strings.Split(excludeType, "|") @@ -448,7 +448,7 @@ func NewProxiesParser(filter string, excludeFilter string, excludeType string, d } } - proxy, err := adapter.ParseProxy(mapping) + proxy, err := adapter.ParseProxy(mapping, adapter.WithProviderName(pdName)) if err != nil { return nil, fmt.Errorf("proxy %d error: %w", idx, err) } diff --git a/mihomo/common/structure/structure.go b/mihomo/common/structure/structure.go index 840e4f6cad..d43dec033e 100644 --- a/mihomo/common/structure/structure.go +++ b/mihomo/common/structure/structure.go @@ -517,6 +517,10 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e fieldName = tagValue } + if tagValue == "-" { + continue + } + rawMapKey := reflect.ValueOf(fieldName) rawMapVal := dataVal.MapIndex(rawMapKey) if !rawMapVal.IsValid() { diff --git a/mihomo/common/structure/structure_test.go b/mihomo/common/structure/structure_test.go index 06dffd0f32..c79c2eb46f 100644 --- a/mihomo/common/structure/structure_test.go +++ b/mihomo/common/structure/structure_test.go @@ -308,3 +308,27 @@ func TestStructure_Ignore(t *testing.T) { assert.Nil(t, err) assert.Equal(t, s.MustIgnore, "oldData") } + +func TestStructure_IgnoreInNest(t *testing.T) { + rawMap := map[string]any{ + "-": "newData", + } + + type TP struct { + MustIgnore string `test:"-"` + } + + s := struct { + TP + }{TP{MustIgnore: "oldData"}} + + err := decoder.Decode(rawMap, &s) + assert.Nil(t, err) + assert.Equal(t, s.MustIgnore, "oldData") + + // test omitempty + delete(rawMap, "-") + err = decoder.Decode(rawMap, &s) + assert.Nil(t, err) + assert.Equal(t, s.MustIgnore, "oldData") +} diff --git a/mihomo/constant/adapters.go b/mihomo/constant/adapters.go index 0b9098fd69..07ae5de1d8 100644 --- a/mihomo/constant/adapters.go +++ b/mihomo/constant/adapters.go @@ -59,6 +59,7 @@ var ErrNotSupport = errors.New("no support") type Connection interface { Chains() Chain + ProviderChains() Chain AppendToChains(adapter ProxyAdapter) RemoteDestination() string } @@ -102,13 +103,14 @@ type Dialer interface { } type ProxyInfo struct { - XUDP bool - TFO bool - MPTCP bool - SMUX bool - Interface string - RoutingMark int - DialerProxy string + XUDP bool + TFO bool + MPTCP bool + SMUX bool + Interface string + RoutingMark int + ProviderName string + DialerProxy string } type ProxyAdapter interface { diff --git a/mihomo/constant/dns.go b/mihomo/constant/dns.go index 13a2ed362a..79dafd2bb9 100644 --- a/mihomo/constant/dns.go +++ b/mihomo/constant/dns.go @@ -86,12 +86,17 @@ func (d DNSPrefer) String() string { } } -func NewDNSPrefer(prefer string) DNSPrefer { - if p, ok := dnsPreferMap[prefer]; ok { - return p - } else { - return DualStack +func (d DNSPrefer) MarshalText() ([]byte, error) { + return []byte(d.String()), nil +} + +func (d *DNSPrefer) UnmarshalText(data []byte) error { + p, exist := dnsPreferMap[strings.ToLower(string(data))] + if !exist { + p = DualStack } + *d = p + return nil } // FilterModeMapping is a mapping for FilterMode enum diff --git a/mihomo/tunnel/statistic/tracker.go b/mihomo/tunnel/statistic/tracker.go index ffa2e71ede..2fbb1cf9e3 100644 --- a/mihomo/tunnel/statistic/tracker.go +++ b/mihomo/tunnel/statistic/tracker.go @@ -28,6 +28,7 @@ type TrackerInfo struct { DownloadTotal atomic.Int64 `json:"download"` Start time.Time `json:"start"` Chain C.Chain `json:"chains"` + ProviderChain C.Chain `json:"providerChains"` Rule string `json:"rule"` RulePayload string `json:"rulePayload"` } @@ -126,6 +127,7 @@ func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.R Start: time.Now(), Metadata: metadata, Chain: conn.Chains(), + ProviderChain: conn.ProviderChains(), Rule: "", UploadTotal: atomic.NewInt64(uploadTotal), DownloadTotal: atomic.NewInt64(downloadTotal), @@ -217,6 +219,7 @@ func NewUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, ru Start: time.Now(), Metadata: metadata, Chain: conn.Chains(), + ProviderChain: conn.ProviderChains(), Rule: "", UploadTotal: atomic.NewInt64(uploadTotal), DownloadTotal: atomic.NewInt64(downloadTotal), diff --git a/openwrt-passwall/luci-app-passwall/luasrc/controller/passwall.lua b/openwrt-passwall/luci-app-passwall/luasrc/controller/passwall.lua index e46862f4b1..0c7400dd5a 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/controller/passwall.lua +++ b/openwrt-passwall/luci-app-passwall/luasrc/controller/passwall.lua @@ -82,6 +82,7 @@ function index() entry({"admin", "services", appname, "copy_node"}, call("copy_node")).leaf = true entry({"admin", "services", appname, "clear_all_nodes"}, call("clear_all_nodes")).leaf = true entry({"admin", "services", appname, "delete_select_nodes"}, call("delete_select_nodes")).leaf = true + entry({"admin", "services", appname, "reassign_group"}, call("reassign_group")).leaf = true entry({"admin", "services", appname, "get_node"}, call("get_node")).leaf = true entry({"admin", "services", appname, "save_node_order"}, call("save_node_order")).leaf = true entry({"admin", "services", appname, "update_rules"}, call("update_rules")).leaf = true @@ -640,6 +641,20 @@ function save_node_order() http_write_json({ status = "ok" }) end +function reassign_group() + local ids = http.formvalue("ids") or "" + local group = http.formvalue("group") or "default" + for id in ids:gmatch("([^,]+)") do + if group ~="" and group ~= "default" then + api.sh_uci_set(appname, id, "group", group) + else + api.sh_uci_del(appname, id, "group") + end + end + api.sh_uci_commit(appname) + http_write_json({ status = "ok" }) +end + function update_rules() local update = http.formvalue("update") luci.sys.call("lua /usr/share/passwall/rule_update.lua log '" .. update .. "' > /dev/null 2>&1 &") diff --git a/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/node_list/link_add_node.htm b/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/node_list/link_add_node.htm index 3caee6fb99..63863ee27e 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/node_list/link_add_node.htm +++ b/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/node_list/link_add_node.htm @@ -41,12 +41,16 @@ local api = require "luci.passwall.api" } function open_add_link_div() { + document.getElementById('modal-mask').style.display = 'block'; document.getElementById("add_link_div").style.display = "block"; + document.body.classList.add('modal-open'); document.getElementById("nodes_link").focus(); } function close_add_link_div() { + document.getElementById('modal-mask').style.display = 'none'; document.getElementById("add_link_div").style.display = "none"; + document.body.classList.remove('modal-open'); } function add_node() { @@ -81,13 +85,69 @@ local api = require "luci.passwall.api" } } + function open_reassign_group_div() { + var ids = []; + var visibleContainer = document.querySelector('#cbi-passwall-nodes > .cbi-tabcontainer[style*="display:block"], #cbi-passwall-nodes > .cbi-tabcontainer[style*="display: block"]'); + if (!visibleContainer) return; + var doms = visibleContainer.getElementsByClassName("nodes_select"); + if (doms && doms.length > 0) { + for (var i = 0 ; i < doms.length; i++) { + if (doms[i].checked) { + ids.push(doms[i].getAttribute("cbid")) + } + } + if (ids.length > 0) { + document.getElementById('modal-mask').style.display = 'block'; + document.getElementById("reassign_group_div").style.display = "block"; + document.body.classList.add('modal-open'); + } else { + alert("<%:You no select nodes !%>"); + } + } + } + + function close_reassign_group_div() { + document.getElementById('modal-mask').style.display = 'none'; + document.getElementById("reassign_group_div").style.display = "none"; + document.body.classList.remove('modal-open'); + } + + function reassign_group() { + var ids = []; + var group = (document.querySelector('#reassign_group_custom input[type="hidden"]')?.value || "default"); + var visibleContainer = document.querySelector('#cbi-passwall-nodes > .cbi-tabcontainer[style*="display:block"], #cbi-passwall-nodes > .cbi-tabcontainer[style*="display: block"]'); + if (!visibleContainer) return; + var doms = visibleContainer.getElementsByClassName("nodes_select"); + if (doms && doms.length > 0) { + for (var i = 0 ; i < doms.length; i++) { + if (doms[i].checked) { + ids.push(doms[i].getAttribute("cbid")) + } + } + if (ids.length > 0) { + XHR.get('<%=api.url("reassign_group")%>', { + group: group, + ids: ids.join(",") + }, + function(x, data) { + if (x && x.status == 200) { + window.location.href = '<%=api.url("node_list")%>'; + } + else { + alert("<%:Error%>"); + } + }); + } + } + } + function add_new_node() { window.location.href = '<%=api.url("add_node")%>?redirect=1'; } //自定义分组下拉列表事件 - document.addEventListener("DOMContentLoaded", function() { - var dropdown = document.getElementById("addlink_group_custom"); + function dropdown_list_fun(div_id) { + var dropdown = document.getElementById(div_id); if (!dropdown) return; var display = dropdown.querySelector(".selected-display"); @@ -99,7 +159,7 @@ local api = require "luci.passwall.api" display.addEventListener("click", function() { list.style.display = list.style.display === "none" ? "block" : "none"; input.value = ""; - input.focus(); + //input.focus(); }); function selectItem(li) { @@ -119,7 +179,8 @@ local api = require "luci.passwall.api" }); input.addEventListener("keydown", function(e){ - if (e.keyCode !== 13) return; + var isEnter = e.key === "Enter" || e.keyCode === 13; + if (!isEnter) return; e.stopPropagation(); e.preventDefault(); @@ -135,7 +196,7 @@ local api = require "luci.passwall.api" } var li = Array.from(list.querySelectorAll(".dropdown-item")).find(function(el){ - return el.dataset.value.toLowerCase() === val.toLowerCase(); + return el.dataset.value && el.dataset.value.toLowerCase() === val.toLowerCase(); }); if (!li) { li = document.createElement("li"); @@ -176,11 +237,20 @@ local api = require "luci.passwall.api" list.style.display = "none"; } }); + } + + document.addEventListener("DOMContentLoaded", function() { + dropdown_list_fun("addlink_group_custom"); }); + document.addEventListener("DOMContentLoaded", function() { + dropdown_list_fun("reassign_group_custom"); + }); //]]> + + +
+
+

<%:Reassign Node Group%>

+
+
+ +
+
+ <%:default%> + +
+ + +
+
+
+
+ + +
+
+
+ + +
@@ -220,9 +320,10 @@ local api = require "luci.passwall.api" - - - + + + +
@@ -239,7 +340,28 @@ local api = require "luci.passwall.api" padding: 5px 0 5px; } - #add_link_div { + #modal-mask { + display: none; + position: fixed; + left: 0; + top: 0; + width: 100vw; + height: 100vh; + background: rgba(0,0,0,0.4); + z-index: 999; + } + + body.modal-open { + overflow: hidden; + pointer-events: none; + } + + body.modal-open #add_link_div, + body.modal-open #reassign_group_div { + pointer-events: auto; + } + + #add_link_div, #reassign_group_div { display: none; position: fixed; left: 50%; @@ -254,7 +376,7 @@ local api = require "luci.passwall.api" max-width: 500px; } - #add_link_modal_container { + #add_link_modal_container, #reassign_group_modal_container { width: 100%; text-align: center; display: flex; @@ -281,7 +403,7 @@ local api = require "luci.passwall.api" width: 100%; } - #add_link_button_container { + #add_link_button_container, #reassign_group_button_container { display: flex; justify-content: space-between; width: 100%; diff --git a/openwrt-passwall/luci-app-passwall/po/zh-cn/passwall.po b/openwrt-passwall/luci-app-passwall/po/zh-cn/passwall.po index 732a4154c7..f6ca3935d1 100644 --- a/openwrt-passwall/luci-app-passwall/po/zh-cn/passwall.po +++ b/openwrt-passwall/luci-app-passwall/po/zh-cn/passwall.po @@ -2008,5 +2008,11 @@ msgstr "配置的类型同样适用于手动导入节点时所指定的核心程 msgid "Group Name" msgstr "分组名" +msgid "Reassign Group" +msgstr "调整分组" + +msgid "Reassign Node Group" +msgstr "调整节点分组" + msgid "Currently using %s node" msgstr "当前使用的 %s 节点" diff --git a/shadowsocks-rust/Dockerfile b/shadowsocks-rust/Dockerfile index b4ec812e15..6565b93530 100644 --- a/shadowsocks-rust/Dockerfile +++ b/shadowsocks-rust/Dockerfile @@ -40,7 +40,7 @@ RUN case "$TARGETARCH" in \ && RUSTFLAGS="-C linker=$CC" CC=$CC cargo build --target "$RUST_TARGET" --release --features "full" \ && mv target/$RUST_TARGET/release/ss* target/release/ -FROM alpine:3.22 AS sslocal +FROM alpine:3.23 AS sslocal # NOTE: Please be careful to change the path of these binaries, refer to #1149 for more information. COPY --from=builder /root/shadowsocks-rust/target/release/sslocal /usr/bin/ @@ -50,7 +50,7 @@ COPY --from=builder /root/shadowsocks-rust/docker/docker-entrypoint.sh /usr/bin/ ENTRYPOINT [ "docker-entrypoint.sh" ] CMD [ "sslocal", "--log-without-time", "-c", "/etc/shadowsocks-rust/config.json" ] -FROM alpine:3.22 AS ssserver +FROM alpine:3.23 AS ssserver COPY --from=builder /root/shadowsocks-rust/target/release/ssserver /usr/bin/ COPY --from=builder /root/shadowsocks-rust/examples/config.json /etc/shadowsocks-rust/ diff --git a/small/luci-app-fchomo/htdocs/luci-static/resources/fchomo.js b/small/luci-app-fchomo/htdocs/luci-static/resources/fchomo.js index fc0dd8cead..f47f66e0a2 100644 --- a/small/luci-app-fchomo/htdocs/luci-static/resources/fchomo.js +++ b/small/luci-app-fchomo/htdocs/luci-static/resources/fchomo.js @@ -124,8 +124,8 @@ const health_checkurls = [ const inbound_type = [ ['http', _('HTTP') + ' - ' + _('TCP')], - ['socks', _('SOCKS') + ' - ' + _('TCP')], - ['mixed', _('Mixed') + ' - ' + _('TCP')], + ['socks', _('SOCKS') + ' - ' + _('TCP/UDP')], + ['mixed', _('Mixed') + ' - ' + _('TCP/UDP')], ['shadowsocks', _('Shadowsocks') + ' - ' + _('TCP/UDP')], ['mieru', _('Mieru') + ' - ' + _('TCP/UDP')], ['sudoku', _('Sudoku') + ' - ' + _('TCP')], @@ -156,7 +156,7 @@ const load_balance_strategy = [ const outbound_type = [ ['direct', _('DIRECT') + ' - ' + _('TCP/UDP')], ['http', _('HTTP') + ' - ' + _('TCP')], - ['socks5', _('SOCKS5') + ' - ' + _('TCP')], + ['socks5', _('SOCKS5') + ' - ' + _('TCP/UDP')], ['ss', _('Shadowsocks') + ' - ' + _('TCP/UDP')], //['ssr', _('ShadowsocksR')], // Deprecated ['mieru', _('Mieru') + ' - ' + _('TCP/UDP')], diff --git a/small/v2ray-geodata/Makefile b/small/v2ray-geodata/Makefile index e1b7aacc66..56c0e62254 100644 --- a/small/v2ray-geodata/Makefile +++ b/small/v2ray-geodata/Makefile @@ -21,13 +21,13 @@ define Download/geoip HASH:=2445b44d9ae3ab9a867c9d1e0e244646c4c378622e14b9afaf3658ecf46a40b9 endef -GEOSITE_VER:=20251203115157 +GEOSITE_VER:=20251204062603 GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER) define Download/geosite URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/ URL_FILE:=dlc.dat FILE:=$(GEOSITE_FILE) - HASH:=0a14ebf9162fd0e3486b15a40025b9c807d2d38dd4379830edbbb113ee75ea80 + HASH:=3355697a91bb72c485aba8fb1748c202573da19b42f4cb050d1a72594411c34d endef GEOSITE_IRAN_VER:=202512010051 diff --git a/v2rayn/.github/workflows/build-linux.yml b/v2rayn/.github/workflows/build-linux.yml index 036c8f3475..d5c40e576f 100644 --- a/v2rayn/.github/workflows/build-linux.yml +++ b/v2rayn/.github/workflows/build-linux.yml @@ -31,7 +31,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6.0.0 + uses: actions/checkout@v6.0.1 with: submodules: 'recursive' fetch-depth: '0' @@ -110,7 +110,7 @@ jobs: dnf -y install sudo git rpm-build rpmdevtools dnf-plugins-core rsync findutils tar gzip unzip which - name: Checkout repo (for scripts) - uses: actions/checkout@v6.0.0 + uses: actions/checkout@v6.0.1 with: submodules: 'recursive' fetch-depth: '0' diff --git a/v2rayn/.github/workflows/build-osx.yml b/v2rayn/.github/workflows/build-osx.yml index bace6d3ce9..2143779638 100644 --- a/v2rayn/.github/workflows/build-osx.yml +++ b/v2rayn/.github/workflows/build-osx.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6.0.0 + uses: actions/checkout@v6.0.1 with: submodules: 'recursive' fetch-depth: '0' diff --git a/v2rayn/.github/workflows/build-windows-desktop.yml b/v2rayn/.github/workflows/build-windows-desktop.yml index cc0a644bfc..5da5f3dbdf 100644 --- a/v2rayn/.github/workflows/build-windows-desktop.yml +++ b/v2rayn/.github/workflows/build-windows-desktop.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6.0.0 + uses: actions/checkout@v6.0.1 with: submodules: 'recursive' fetch-depth: '0' diff --git a/v2rayn/.github/workflows/build-windows.yml b/v2rayn/.github/workflows/build-windows.yml index 22f8c8b3a5..82542a6bf1 100644 --- a/v2rayn/.github/workflows/build-windows.yml +++ b/v2rayn/.github/workflows/build-windows.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6.0.0 + uses: actions/checkout@v6.0.1 - name: Setup uses: actions/setup-dotnet@v5.0.1 diff --git a/yt-dlp/README.md b/yt-dlp/README.md index c98c69f418..48f8893411 100644 --- a/yt-dlp/README.md +++ b/yt-dlp/README.md @@ -1871,6 +1871,7 @@ The following extractors use this feature: * `pot_trace`: Enable debug logging for PO Token fetching. Either `true` or `false` (default) * `fetch_pot`: Policy to use for fetching a PO Token from providers. One of `always` (always try fetch a PO Token regardless if the client requires one for the given context), `never` (never fetch a PO Token), or `auto` (default; only fetch a PO Token if the client requires one for the given context) * `jsc_trace`: Enable debug logging for JS Challenge fetching. Either `true` or `false` (default) +* `use_ad_playback_context`: Skip preroll ads to eliminate the mandatory wait period before download. Do NOT use this when passing premium account cookies to yt-dlp, as it will result in a loss of premium formats. Only effective with the `web`, `web_safari`, `web_music` and `mweb` player clients. Either `true` or `false` (default) #### youtube-ejs * `jitless`: Run suported Javascript engines in JIT-less mode. Supported runtimes are `deno`, `node` and `bun`. Provides better security at the cost of performance/speed. Do note that `node` and `bun` are still considered unsecure. Either `true` or `false` (default) diff --git a/yt-dlp/yt_dlp/extractor/archiveorg.py b/yt-dlp/yt_dlp/extractor/archiveorg.py index 3746c58fb7..02c39beb68 100644 --- a/yt-dlp/yt_dlp/extractor/archiveorg.py +++ b/yt-dlp/yt_dlp/extractor/archiveorg.py @@ -704,6 +704,24 @@ class YoutubeWebArchiveIE(InfoExtractor): 'thumbnail': 'https://web.archive.org/web/20160108040020if_/https://i.ytimg.com/vi/SQCom7wjGDs/maxresdefault.jpg', 'upload_date': '20160107', }, + }, { + # dmuxed formats + 'url': 'https://web.archive.org/web/20240922160632/https://www.youtube.com/watch?v=z7hzvTL3k1k', + 'info_dict': { + 'id': 'z7hzvTL3k1k', + 'ext': 'webm', + 'title': 'Praise the Lord and Pass the Ammunition (BARRXN REMIX)', + 'description': 'md5:45dbf2c71c23b0734c8dfb82dd1e94b6', + 'uploader': 'Barrxn', + 'uploader_id': 'TheRockstar6086', + 'uploader_url': 'https://www.youtube.com/user/TheRockstar6086', + 'channel_id': 'UCjJPGUTtvR9uizmawn2ThqA', + 'channel_url': 'https://www.youtube.com/channel/UCjJPGUTtvR9uizmawn2ThqA', + 'duration': 125, + 'thumbnail': r're:https?://.*\.(jpg|webp)', + 'upload_date': '20201207', + }, + 'params': {'format': 'bv'}, }, { 'url': 'https://web.archive.org/web/http://www.youtube.com/watch?v=kH-G_aIBlFw', 'only_matching': True, @@ -1060,6 +1078,19 @@ class YoutubeWebArchiveIE(InfoExtractor): capture_dates.extend([self._OLDEST_CAPTURE_DATE, self._NEWEST_CAPTURE_DATE]) return orderedSet(filter(None, capture_dates)) + def _parse_fmt(self, fmt, extra_info=None): + format_id = traverse_obj(fmt, ('url', {parse_qs}, 'itag', 0)) + return { + 'format_id': format_id, + **self._FORMATS.get(format_id, {}), + **traverse_obj(fmt, { + 'url': ('url', {lambda x: f'https://web.archive.org/web/2id_/{x}'}), + 'ext': ('ext', {str}), + 'filesize': ('url', {parse_qs}, 'clen', 0, {int_or_none}), + }), + **(extra_info or {}), + } + def _real_extract(self, url): video_id, url_date, url_date_2 = self._match_valid_url(url).group('id', 'date', 'date2') url_date = url_date or url_date_2 @@ -1090,17 +1121,14 @@ class YoutubeWebArchiveIE(InfoExtractor): info['thumbnails'] = self._extract_thumbnails(video_id) formats = [] - for fmt in traverse_obj(video_info, ('formats', lambda _, v: url_or_none(v['url']))): - format_id = traverse_obj(fmt, ('url', {parse_qs}, 'itag', 0)) - formats.append({ - 'format_id': format_id, - **self._FORMATS.get(format_id, {}), - **traverse_obj(fmt, { - 'url': ('url', {lambda x: f'https://web.archive.org/web/2id_/{x}'}), - 'ext': ('ext', {str}), - 'filesize': ('url', {parse_qs}, 'clen', 0, {int_or_none}), - }), - }) + if video_info.get('dmux'): + for vf in traverse_obj(video_info, ('formats', 'video', lambda _, v: url_or_none(v['url']))): + formats.append(self._parse_fmt(vf, {'acodec': 'none'})) + for af in traverse_obj(video_info, ('formats', 'audio', lambda _, v: url_or_none(v['url']))): + formats.append(self._parse_fmt(af, {'vcodec': 'none'})) + else: + for fmt in traverse_obj(video_info, ('formats', lambda _, v: url_or_none(v['url']))): + formats.append(self._parse_fmt(fmt)) info['formats'] = formats return info diff --git a/yt-dlp/yt_dlp/extractor/youtube/_base.py b/yt-dlp/yt_dlp/extractor/youtube/_base.py index 9ecce15553..114eee821b 100644 --- a/yt-dlp/yt_dlp/extractor/youtube/_base.py +++ b/yt-dlp/yt_dlp/extractor/youtube/_base.py @@ -104,6 +104,7 @@ INNERTUBE_CLIENTS = { }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 1, 'SUPPORTS_COOKIES': True, + 'SUPPORTS_AD_PLAYBACK_CONTEXT': True, **WEB_PO_TOKEN_POLICIES, }, # Safari UA returns pre-merged video+audio 144p/240p/360p/720p/1080p HLS formats @@ -117,6 +118,7 @@ INNERTUBE_CLIENTS = { }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 1, 'SUPPORTS_COOKIES': True, + 'SUPPORTS_AD_PLAYBACK_CONTEXT': True, **WEB_PO_TOKEN_POLICIES, }, 'web_embedded': { @@ -157,6 +159,7 @@ INNERTUBE_CLIENTS = { ), }, 'SUPPORTS_COOKIES': True, + 'SUPPORTS_AD_PLAYBACK_CONTEXT': True, }, # This client now requires sign-in for every video 'web_creator': { @@ -313,6 +316,7 @@ INNERTUBE_CLIENTS = { ), }, 'SUPPORTS_COOKIES': True, + 'SUPPORTS_AD_PLAYBACK_CONTEXT': True, }, 'tv': { 'INNERTUBE_CONTEXT': { @@ -412,6 +416,7 @@ def build_innertube_clients(): ytcfg.setdefault('SUBS_PO_TOKEN_POLICY', SubsPoTokenPolicy()) ytcfg.setdefault('REQUIRE_AUTH', False) ytcfg.setdefault('SUPPORTS_COOKIES', False) + ytcfg.setdefault('SUPPORTS_AD_PLAYBACK_CONTEXT', False) ytcfg.setdefault('PLAYER_PARAMS', None) ytcfg.setdefault('AUTHENTICATED_USER_AGENT', None) ytcfg['INNERTUBE_CONTEXT']['client'].setdefault('hl', 'en') diff --git a/yt-dlp/yt_dlp/extractor/youtube/_video.py b/yt-dlp/yt_dlp/extractor/youtube/_video.py index a792332046..24c4458d61 100644 --- a/yt-dlp/yt_dlp/extractor/youtube/_video.py +++ b/yt-dlp/yt_dlp/extractor/youtube/_video.py @@ -2629,16 +2629,23 @@ class YoutubeIE(YoutubeBaseInfoExtractor): return {'contentCheckOk': True, 'racyCheckOk': True} @classmethod - def _generate_player_context(cls, sts=None): + def _generate_player_context(cls, sts=None, use_ad_playback_context=False): context = { 'html5Preference': 'HTML5_PREF_WANTS', } if sts is not None: context['signatureTimestamp'] = sts + + playback_context = { + 'contentPlaybackContext': context, + } + if use_ad_playback_context: + playback_context['adPlaybackContext'] = { + 'pyv': True, + } + return { - 'playbackContext': { - 'contentPlaybackContext': context, - }, + 'playbackContext': playback_context, **cls._get_checkok_params(), } @@ -2866,7 +2873,13 @@ class YoutubeIE(YoutubeBaseInfoExtractor): yt_query['serviceIntegrityDimensions'] = {'poToken': po_token} sts = self._extract_signature_timestamp(video_id, player_url, webpage_ytcfg, fatal=False) if player_url else None - yt_query.update(self._generate_player_context(sts)) + + use_ad_playback_context = ( + self._configuration_arg('use_ad_playback_context', ['false'])[0] != 'false' + and traverse_obj(INNERTUBE_CLIENTS, (client, 'SUPPORTS_AD_PLAYBACK_CONTEXT', {bool}))) + + yt_query.update(self._generate_player_context(sts, use_ad_playback_context)) + return self._extract_response( item_id=video_id, ep='player', query=yt_query, ytcfg=player_ytcfg, headers=headers, fatal=True,