diff --git a/Makefile b/Makefile index 3c079aa1..e23fa2d3 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -NAME=Clash.Meta +NAME=clash.meta BINDIR=bin BRANCH=$(shell git branch --show-current) ifeq ($(BRANCH),Alpha) diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go index c318d263..8df84c7c 100644 --- a/adapter/outbound/shadowsocks.go +++ b/adapter/outbound/shadowsocks.go @@ -2,6 +2,7 @@ package outbound import ( "context" + "crypto/tls" "errors" "fmt" "net" @@ -9,12 +10,14 @@ import ( "github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/component/dialer" + tlsC "github.com/Dreamacro/clash/component/tls" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/transport/shadowtls" obfs "github.com/Dreamacro/clash/transport/simple-obfs" "github.com/Dreamacro/clash/transport/socks5" v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin" - "github.com/metacubex/sing-shadowsocks" + shadowsocks "github.com/metacubex/sing-shadowsocks" "github.com/metacubex/sing-shadowsocks/shadowimpl" "github.com/sagernet/sing/common/bufio" M "github.com/sagernet/sing/common/metadata" @@ -27,9 +30,11 @@ type ShadowSocks struct { option *ShadowSocksOption // obfs - obfsMode string - obfsOption *simpleObfsOption - v2rayOption *v2rayObfs.Option + obfsMode string + obfsOption *simpleObfsOption + v2rayOption *v2rayObfs.Option + shadowTLSOption *shadowTLSOption + tlsConfig *tls.Config } type ShadowSocksOption struct { @@ -61,6 +66,13 @@ type v2rayObfsOption struct { Mux bool `obfs:"mux,omitempty"` } +type shadowTLSOption struct { + Password string `obfs:"password"` + Host string `obfs:"host"` + Fingerprint string `obfs:"fingerprint,omitempty"` + SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"` +} + // StreamConn implements C.ProxyAdapter func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { switch ss.obfsMode { @@ -75,6 +87,8 @@ func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e if err != nil { return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) } + case shadowtls.Mode: + c = shadowtls.NewShadowTLS(c, ss.shadowTLSOption.Password, ss.tlsConfig) } if metadata.NetWork == C.UDP && ss.option.UDPOverTCP { return ss.method.DialConn(c, M.ParseSocksaddr(uot.UOTMagicAddress+":443")) @@ -157,6 +171,8 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { var v2rayOption *v2rayObfs.Option var obfsOption *simpleObfsOption + var shadowTLSOpt *shadowTLSOption + var tlsConfig *tls.Config obfsMode := "" decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true}) @@ -192,6 +208,27 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { v2rayOption.TLS = true v2rayOption.SkipCertVerify = opts.SkipCertVerify } + } else if option.Plugin == shadowtls.Mode { + obfsMode = shadowtls.Mode + shadowTLSOpt = &shadowTLSOption{} + if err := decoder.Decode(option.PluginOpts, shadowTLSOpt); err != nil { + return nil, fmt.Errorf("ss %s initialize shadow-tls-plugin error: %w", addr, err) + } + + tlsConfig = &tls.Config{ + NextProtos: shadowtls.DefaultALPN, + MinVersion: tls.VersionTLS12, + InsecureSkipVerify: shadowTLSOpt.SkipCertVerify, + ServerName: shadowTLSOpt.Host, + } + + if len(shadowTLSOpt.Fingerprint) == 0 { + tlsConfig = tlsC.GetGlobalFingerprintTLCConfig(tlsConfig) + } else { + if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, shadowTLSOpt.Fingerprint); err != nil { + return nil, err + } + } } return &ShadowSocks{ @@ -206,10 +243,12 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { }, method: method, - option: &option, - obfsMode: obfsMode, - v2rayOption: v2rayOption, - obfsOption: obfsOption, + option: &option, + obfsMode: obfsMode, + v2rayOption: v2rayOption, + obfsOption: obfsOption, + shadowTLSOption: shadowTLSOpt, + tlsConfig: tlsConfig, }, nil } diff --git a/adapter/outbound/trojan.go b/adapter/outbound/trojan.go index 698db598..e7928b50 100644 --- a/adapter/outbound/trojan.go +++ b/adapter/outbound/trojan.go @@ -4,12 +4,12 @@ import ( "context" "crypto/tls" "fmt" - tlsC "github.com/Dreamacro/clash/component/tls" "net" "net/http" "strconv" "github.com/Dreamacro/clash/component/dialer" + tlsC "github.com/Dreamacro/clash/component/tls" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/transport/gun" "github.com/Dreamacro/clash/transport/trojan" @@ -219,13 +219,16 @@ func NewTrojan(option TrojanOption) (*Trojan, error) { Fingerprint: option.Fingerprint, } - if option.Network != "ws" && len(option.Flow) >= 16 { - option.Flow = option.Flow[:16] - switch option.Flow { - case vless.XRO, vless.XRD, vless.XRS: - tOption.Flow = option.Flow - default: - return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow) + switch option.Network { + case "", "tcp": + if len(option.Flow) >= 16 { + option.Flow = option.Flow[:16] + switch option.Flow { + case vless.XRO, vless.XRD, vless.XRS: + tOption.Flow = option.Flow + default: + return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow) + } } } @@ -273,11 +276,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) { } } - if t.option.Flow != "" { - t.transport = gun.NewHTTP2XTLSClient(dialFn, tlsConfig) - } else { - t.transport = gun.NewHTTP2Client(dialFn, tlsConfig) - } + t.transport = gun.NewHTTP2Client(dialFn, tlsConfig) t.gunTLSConfig = tlsConfig t.gunConfig = &gun.Config{ diff --git a/adapter/outbound/vless.go b/adapter/outbound/vless.go index 06627912..e9e382c4 100644 --- a/adapter/outbound/vless.go +++ b/adapter/outbound/vless.go @@ -12,6 +12,10 @@ import ( "strconv" "sync" + vmessSing "github.com/sagernet/sing-vmess" + "github.com/sagernet/sing-vmess/packetaddr" + M "github.com/sagernet/sing/common/metadata" + "github.com/Dreamacro/clash/common/convert" "github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/resolver" @@ -49,6 +53,9 @@ type VlessOption struct { FlowShow bool `proxy:"flow-show,omitempty"` TLS bool `proxy:"tls,omitempty"` UDP bool `proxy:"udp,omitempty"` + PacketAddr bool `proxy:"packet-addr,omitempty"` + XUDP bool `proxy:"xudp,omitempty"` + PacketEncoding string `proxy:"packet-encoding,omitempty"` Network string `proxy:"network,omitempty"` HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"` HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"` @@ -137,11 +144,7 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { c, err = vmess.StreamH2Conn(c, h2Opts) case "grpc": - if v.isXTLSEnabled() { - c, err = gun.StreamGunWithXTLSConn(c, v.gunTLSConfig, v.gunConfig) - } else { - c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig) - } + c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig) default: // default tcp network // handle TLS And XTLS @@ -152,21 +155,17 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { return nil, err } - return v.client.StreamConn(c, parseVlessAddr(metadata)) + return v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP)) } func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error) { host, _, _ := net.SplitHostPort(v.addr) - if v.isXTLSEnabled() { + if v.isXTLSEnabled() && !isH2 { xtlsOpts := vless.XTLSConfig{ Host: host, SkipCertVerify: v.option.SkipCertVerify, - FingerPrint: v.option.Fingerprint, - } - - if isH2 { - xtlsOpts.NextProtos = []string{"h2"} + Fingerprint: v.option.Fingerprint, } if v.option.ServerName != "" { @@ -212,7 +211,7 @@ func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d safeConnClose(c, err) }(c) - c, err = v.client.StreamConn(c, parseVlessAddr(metadata)) + c, err = v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP)) if err != nil { return nil, err } @@ -259,7 +258,15 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o safeConnClose(c, err) }(c) - c, err = v.client.StreamConn(c, parseVlessAddr(metadata)) + if v.option.PacketAddr { + packetAddrMetadata := *metadata // make a copy + packetAddrMetadata.Host = packetaddr.SeqPacketMagicAddress + packetAddrMetadata.DstPort = "443" + + c, err = v.client.StreamConn(c, parseVlessAddr(&packetAddrMetadata, false)) + } else { + c, err = v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP)) + } if err != nil { return nil, fmt.Errorf("new vless client error: %v", err) @@ -289,7 +296,15 @@ func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met safeConnClose(c, err) }(c) - c, err = v.StreamConn(c, metadata) + if v.option.PacketAddr { + packetAddrMetadata := *metadata // make a copy + packetAddrMetadata.Host = packetaddr.SeqPacketMagicAddress + packetAddrMetadata.DstPort = "443" + + c, err = v.StreamConn(c, &packetAddrMetadata) + } else { + c, err = v.StreamConn(c, metadata) + } if err != nil { return nil, fmt.Errorf("new vless client error: %v", err) @@ -305,6 +320,17 @@ func (v *Vless) SupportWithDialer() bool { // ListenPacketOnStreamConn implements C.ProxyAdapter func (v *Vless) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) { + if v.option.XUDP { + return newPacketConn(&threadSafePacketConn{ + PacketConn: vmessSing.NewXUDPConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), + }, v), nil + } else if v.option.PacketAddr { + return newPacketConn(&threadSafePacketConn{ + PacketConn: packetaddr.NewConn(&vlessPacketConn{ + Conn: c, rAddr: metadata.UDPAddr(), + }, M.ParseSocksaddr(metadata.RemoteAddress())), + }, v), nil + } return newPacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil } @@ -313,7 +339,7 @@ func (v *Vless) SupportUOT() bool { return true } -func parseVlessAddr(metadata *C.Metadata) *vless.DstAddr { +func parseVlessAddr(metadata *C.Metadata, xudp bool) *vless.DstAddr { var addrType byte var addr []byte switch metadata.AddrType() { @@ -337,7 +363,8 @@ func parseVlessAddr(metadata *C.Metadata) *vless.DstAddr { UDP: metadata.NetWork == C.UDP, AddrType: addrType, Addr: addr, - Port: uint(port), + Port: uint16(port), + Mux: metadata.NetWork == C.UDP && xudp, } } @@ -459,12 +486,23 @@ func NewVless(option VlessOption) (*Vless, error) { tp: C.Vless, udp: option.UDP, iface: option.Interface, + rmark: option.RoutingMark, prefer: C.NewDNSPrefer(option.IPVersion), }, client: client, option: &option, } + switch option.PacketEncoding { + case "packetaddr", "packet": + option.PacketAddr = true + case "xudp": + option.XUDP = true + } + if option.XUDP { + option.PacketAddr = false + } + switch option.Network { case "h2": if len(option.HTTP2Opts.Host) == 0 { @@ -497,11 +535,7 @@ func NewVless(option VlessOption) (*Vless, error) { v.gunTLSConfig = tlsConfig v.gunConfig = gunConfig - if v.isXTLSEnabled() { - v.transport = gun.NewHTTP2XTLSClient(dialFn, tlsConfig) - } else { - v.transport = gun.NewHTTP2Client(dialFn, tlsConfig) - } + v.transport = gun.NewHTTP2Client(dialFn, tlsConfig) } return v, nil diff --git a/adapter/outbound/vmess.go b/adapter/outbound/vmess.go index 8d018caa..4d3bb3d7 100644 --- a/adapter/outbound/vmess.go +++ b/adapter/outbound/vmess.go @@ -116,7 +116,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { if len(v.option.Fingerprint) == 0 { wsOpts.TLSConfig = tlsC.GetGlobalFingerprintTLCConfig(tlsConfig) } else { - var err error if wsOpts.TLSConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint); err != nil { return nil, err } @@ -290,16 +289,6 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o } return v.ListenPacketOnStreamConn(c, metadata) } - c, err = dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...) - if err != nil { - return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) - } - tcpKeepAlive(c) - defer func(c net.Conn) { - safeConnClose(c, err) - }(c) - - c, err = v.StreamConn(c, metadata) return v.ListenPacketWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata) } @@ -315,10 +304,18 @@ func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met } c, err := dialer.DialContext(ctx, "tcp", v.addr) + if err != nil { + return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) + } + tcpKeepAlive(c) + defer func(c net.Conn) { + safeConnClose(c, err) + }(c) + + c, err = v.StreamConn(c, metadata) if err != nil { return nil, fmt.Errorf("new vmess client error: %v", err) } - return v.ListenPacketOnStreamConn(c, metadata) } diff --git a/adapter/outboundgroup/fallback.go b/adapter/outboundgroup/fallback.go index 0a4dab41..34365d0e 100644 --- a/adapter/outboundgroup/fallback.go +++ b/adapter/outboundgroup/fallback.go @@ -4,11 +4,12 @@ import ( "context" "encoding/json" "errors" + "time" + "github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/constant/provider" - "time" ) type Fallback struct { @@ -132,6 +133,7 @@ func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider) }, option.Filter, option.ExcludeFilter, + option.ExcludeType, providers, }), disableUDP: option.DisableUDP, diff --git a/adapter/outboundgroup/groupbase.go b/adapter/outboundgroup/groupbase.go index 426a1282..fee24bd6 100644 --- a/adapter/outboundgroup/groupbase.go +++ b/adapter/outboundgroup/groupbase.go @@ -3,23 +3,26 @@ package outboundgroup import ( "context" "fmt" + "strings" + "sync" + "time" + "github.com/Dreamacro/clash/adapter/outbound" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/constant/provider" types "github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/tunnel" + "github.com/dlclark/regexp2" "go.uber.org/atomic" - "strings" - "sync" - "time" ) type GroupBase struct { *outbound.Base filterRegs []*regexp2.Regexp excludeFilterReg *regexp2.Regexp + excludeTypeArray []string providers []provider.ProxyProvider failedTestMux sync.Mutex failedTimes int @@ -33,6 +36,7 @@ type GroupBaseOption struct { outbound.BaseOption filter string excludeFilter string + excludeType string providers []provider.ProxyProvider } @@ -41,6 +45,10 @@ func NewGroupBase(opt GroupBaseOption) *GroupBase { if opt.excludeFilter != "" { excludeFilterReg = regexp2.MustCompile(opt.excludeFilter, 0) } + var excludeTypeArray []string + if opt.excludeType != "" { + excludeTypeArray = strings.Split(opt.excludeType, "|") + } var filterRegs []*regexp2.Regexp if opt.filter != "" { @@ -54,6 +62,7 @@ func NewGroupBase(opt GroupBaseOption) *GroupBase { Base: outbound.NewBase(opt.BaseOption), filterRegs: filterRegs, excludeFilterReg: excludeFilterReg, + excludeTypeArray: excludeTypeArray, providers: opt.providers, failedTesting: atomic.NewBool(false), } @@ -148,6 +157,24 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy { } proxies = newProxies } + if gb.excludeTypeArray != nil { + var newProxies []C.Proxy + for _, p := range proxies { + mType := p.Type().String() + flag := false + for i := range gb.excludeTypeArray { + if strings.EqualFold(mType, gb.excludeTypeArray[i]) { + flag = true + } + + } + if flag { + continue + } + newProxies = append(newProxies, p) + } + proxies = newProxies + } if gb.excludeFilterReg != nil { var newProxies []C.Proxy diff --git a/adapter/outboundgroup/loadbalance.go b/adapter/outboundgroup/loadbalance.go index 7f875451..7dc4d3d3 100644 --- a/adapter/outboundgroup/loadbalance.go +++ b/adapter/outboundgroup/loadbalance.go @@ -5,11 +5,11 @@ import ( "encoding/json" "errors" "fmt" - "github.com/Dreamacro/clash/common/cache" "net" "time" "github.com/Dreamacro/clash/adapter/outbound" + "github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/common/murmur3" "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" @@ -229,6 +229,7 @@ func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvide }, option.Filter, option.ExcludeFilter, + option.ExcludeType, providers, }), strategyFn: strategyFn, diff --git a/adapter/outboundgroup/parser.go b/adapter/outboundgroup/parser.go index 53a82a60..ebae562a 100644 --- a/adapter/outboundgroup/parser.go +++ b/adapter/outboundgroup/parser.go @@ -31,6 +31,7 @@ type GroupCommonOption struct { DisableUDP bool `group:"disable-udp,omitempty"` Filter string `group:"filter,omitempty"` ExcludeFilter string `group:"exclude-filter,omitempty"` + ExcludeType string `group:"exclude-type,omitempty"` } func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider) (C.ProxyAdapter, error) { diff --git a/adapter/outboundgroup/relay.go b/adapter/outboundgroup/relay.go index 43ef81c9..b466b1ff 100644 --- a/adapter/outboundgroup/relay.go +++ b/adapter/outboundgroup/relay.go @@ -191,6 +191,7 @@ func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Re }, "", "", + "", providers, }), } diff --git a/adapter/outboundgroup/selector.go b/adapter/outboundgroup/selector.go index 71ebacce..6356d10e 100644 --- a/adapter/outboundgroup/selector.go +++ b/adapter/outboundgroup/selector.go @@ -100,6 +100,7 @@ func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) }, option.Filter, option.ExcludeFilter, + option.ExcludeType, providers, }), selected: "COMPATIBLE", diff --git a/adapter/outboundgroup/urltest.go b/adapter/outboundgroup/urltest.go index 55c1cc7c..27cef9c6 100644 --- a/adapter/outboundgroup/urltest.go +++ b/adapter/outboundgroup/urltest.go @@ -144,6 +144,7 @@ func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, o option.Filter, option.ExcludeFilter, + option.ExcludeType, providers, }), fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10), diff --git a/adapter/provider/parser.go b/adapter/provider/parser.go index e55ab914..fc5ed936 100644 --- a/adapter/provider/parser.go +++ b/adapter/provider/parser.go @@ -3,10 +3,10 @@ package provider import ( "errors" "fmt" - "github.com/Dreamacro/clash/component/resource" "time" "github.com/Dreamacro/clash/common/structure" + "github.com/Dreamacro/clash/component/resource" C "github.com/Dreamacro/clash/constant" types "github.com/Dreamacro/clash/constant/provider" ) @@ -27,6 +27,7 @@ type proxyProviderSchema struct { Interval int `provider:"interval,omitempty"` Filter string `provider:"filter,omitempty"` ExcludeFilter string `provider:"exclude-filter,omitempty"` + ExcludeType string `provider:"exclude-type,omitempty"` HealthCheck healthCheckSchema `provider:"health-check,omitempty"` } @@ -63,5 +64,7 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide interval := time.Duration(uint(schema.Interval)) * time.Second filter := schema.Filter excludeFilter := schema.ExcludeFilter - return NewProxySetProvider(name, interval, filter, excludeFilter, vehicle, hc) + excludeType := schema.ExcludeType + + return NewProxySetProvider(name, interval, filter, excludeFilter, excludeType, vehicle, hc) } diff --git a/adapter/provider/provider.go b/adapter/provider/provider.go index 51f536fe..7d7ba977 100644 --- a/adapter/provider/provider.go +++ b/adapter/provider/provider.go @@ -5,8 +5,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/dlclark/regexp2" - "gopkg.in/yaml.v3" "net/http" "runtime" "strings" @@ -19,6 +17,9 @@ import ( C "github.com/Dreamacro/clash/constant" types "github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/log" + + "github.com/dlclark/regexp2" + "gopkg.in/yaml.v3" ) const ( @@ -141,11 +142,16 @@ func stopProxyProvider(pd *ProxySetProvider) { _ = pd.Fetcher.Destroy() } -func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) { +func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string, excludeType string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) { excludeFilterReg, err := regexp2.Compile(excludeFilter, 0) if err != nil { return nil, fmt.Errorf("invalid excludeFilter regex: %w", err) } + var excludeTypeArray []string + if excludeType != "" { + excludeTypeArray = strings.Split(excludeType, "|") + } + var filterRegs []*regexp2.Regexp for _, filter := range strings.Split(filter, "`") { filterReg, err := regexp2.Compile(filter, 0) @@ -164,7 +170,7 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, exc healthCheck: hc, } - fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, filterRegs, excludeFilterReg), proxiesOnUpdate(pd)) + fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg), proxiesOnUpdate(pd)) pd.Fetcher = fetcher pd.getSubscriptionInfo() @@ -262,7 +268,7 @@ func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) { } } -func proxiesParseAndFilter(filter string, excludeFilter string, filterRegs []*regexp2.Regexp, excludeFilterReg *regexp2.Regexp) resource.Parser[[]C.Proxy] { +func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray []string, filterRegs []*regexp2.Regexp, excludeFilterReg *regexp2.Regexp) resource.Parser[[]C.Proxy] { return func(buf []byte) ([]C.Proxy, error) { schema := &ProxySchema{} @@ -282,6 +288,27 @@ func proxiesParseAndFilter(filter string, excludeFilter string, filterRegs []*re proxiesSet := map[string]struct{}{} for _, filterReg := range filterRegs { for idx, mapping := range schema.Proxies { + if nil != excludeTypeArray && len(excludeTypeArray) > 0 { + mType, ok := mapping["type"] + if !ok { + continue + } + pType, ok := mType.(string) + if !ok { + continue + } + flag := false + for i := range excludeTypeArray { + if strings.EqualFold(pType, excludeTypeArray[i]) { + flag = true + } + + } + if flag { + continue + } + + } mName, ok := mapping["name"] if !ok { continue diff --git a/common/convert/converter.go b/common/convert/converter.go index 24043a41..89b4d95f 100644 --- a/common/convert/converter.go +++ b/common/convert/converter.go @@ -144,14 +144,6 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { if encryption := query.Get("encryption"); encryption != "" { vmess["cipher"] = encryption } - if packetEncoding := query.Get("packetEncoding"); packetEncoding != "" { - switch packetEncoding { - case "packet": - vmess["packet-addr"] = true - case "xudp": - vmess["xudp"] = true - } - } proxies = append(proxies, vmess) continue } @@ -162,8 +154,11 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { if jsonDc.Decode(&values) != nil { continue } - - name := uniqueName(names, values["ps"].(string)) + tempName, ok := values["ps"].(string) + if !ok { + continue + } + name := uniqueName(names, tempName) vmess := make(map[string]any, 20) vmess["name"] = name @@ -177,6 +172,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { vmess["alterId"] = 0 } vmess["udp"] = true + vmess["xudp"] = true vmess["tls"] = false vmess["skip-cert-verify"] = false @@ -272,19 +268,22 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { } var ( - cipher = urlSS.User.Username() - password string + cipherRaw = urlSS.User.Username() + cipher string + password string ) if password, found = urlSS.User.Password(); !found { - dcBuf, _ := enc.DecodeString(cipher) - if !strings.Contains(string(dcBuf), "2022-blake3") { - dcBuf, _ = encRaw.DecodeString(cipher) - } + dcBuf, _ := enc.DecodeString(cipherRaw) cipher, password, found = strings.Cut(string(dcBuf), ":") if !found { continue } + err := VerifyMethod(cipher, password) + if err != nil { + dcBuf, _ := encRaw.DecodeString(cipherRaw) + cipher, password, found = strings.Cut(string(dcBuf), ":") + } } ss := make(map[string]any, 10) diff --git a/common/convert/util.go b/common/convert/util.go index b3bd6e5b..03a48ecd 100644 --- a/common/convert/util.go +++ b/common/convert/util.go @@ -2,6 +2,7 @@ package convert import ( "encoding/base64" + "github.com/metacubex/sing-shadowsocks/shadowimpl" "math/rand" "net/http" "strings" @@ -314,3 +315,8 @@ func SetUserAgent(header http.Header) { userAgent := RandUserAgent() header.Set("User-Agent", userAgent) } + +func VerifyMethod(cipher, password string) (err error) { + _, err = shadowimpl.FetchMethod(cipher, password) + return +} diff --git a/common/convert/v.go b/common/convert/v.go index 0e3960f0..29be4a9f 100644 --- a/common/convert/v.go +++ b/common/convert/v.go @@ -25,6 +25,14 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m proxy["servername"] = sni } + switch query.Get("packetEncoding") { + case "none": + case "packet": + proxy["packet-addr"] = true + default: + proxy["xudp"] = true + } + network := strings.ToLower(query.Get("type")) if network == "" { network = "tcp" diff --git a/component/geodata/init.go b/component/geodata/init.go index 2b36b626..f7dd7a9e 100644 --- a/component/geodata/init.go +++ b/component/geodata/init.go @@ -2,6 +2,7 @@ package geodata import ( "fmt" + "github.com/Dreamacro/clash/component/mmdb" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" "io" @@ -9,7 +10,8 @@ import ( "os" ) -var initFlag bool +var initGeoSite bool +var initGeoIP int func InitGeoSite() error { if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) { @@ -18,8 +20,9 @@ func InitGeoSite() error { return fmt.Errorf("can't download GeoSite.dat: %s", err.Error()) } log.Infoln("Download GeoSite.dat finish") + initGeoSite = false } - if !initFlag { + if !initGeoSite { if err := Verify(C.GeositeName); err != nil { log.Warnln("GeoSite.dat invalid, remove and download: %s", err) if err := os.Remove(C.Path.GeoSite()); err != nil { @@ -29,7 +32,7 @@ func InitGeoSite() error { return fmt.Errorf("can't download GeoSite.dat: %s", err.Error()) } } - initFlag = true + initGeoSite = true } return nil } @@ -50,3 +53,68 @@ func downloadGeoSite(path string) (err error) { return err } + +func downloadGeoIP(path string) (err error) { + resp, err := http.Get(C.GeoIpUrl) + if err != nil { + return + } + defer resp.Body.Close() + + f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644) + if err != nil { + return err + } + defer f.Close() + _, err = io.Copy(f, resp.Body) + + return err +} + +func InitGeoIP() error { + if C.GeodataMode { + if _, err := os.Stat(C.Path.GeoIP()); os.IsNotExist(err) { + log.Infoln("Can't find GeoIP.dat, start download") + if err := downloadGeoIP(C.Path.GeoIP()); err != nil { + return fmt.Errorf("can't download GeoIP.dat: %s", err.Error()) + } + log.Infoln("Download GeoIP.dat finish") + initGeoIP = 0 + } + + if initGeoIP != 1 { + if err := Verify(C.GeoipName); err != nil { + log.Warnln("GeoIP.dat invalid, remove and download: %s", err) + if err := os.Remove(C.Path.GeoIP()); err != nil { + return fmt.Errorf("can't remove invalid GeoIP.dat: %s", err.Error()) + } + if err := downloadGeoIP(C.Path.GeoIP()); err != nil { + return fmt.Errorf("can't download GeoIP.dat: %s", err.Error()) + } + } + initGeoIP = 1 + } + return nil + } + + if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) { + log.Infoln("Can't find MMDB, start download") + if err := mmdb.DownloadMMDB(C.Path.MMDB()); err != nil { + return fmt.Errorf("can't download MMDB: %s", err.Error()) + } + } + + if initGeoIP != 2 { + if !mmdb.Verify() { + log.Warnln("MMDB invalid, remove and download") + if err := os.Remove(C.Path.MMDB()); err != nil { + return fmt.Errorf("can't remove invalid MMDB: %s", err.Error()) + } + if err := mmdb.DownloadMMDB(C.Path.MMDB()); err != nil { + return fmt.Errorf("can't download MMDB: %s", err.Error()) + } + } + initGeoIP = 2 + } + return nil +} diff --git a/component/mmdb/mmdb.go b/component/mmdb/mmdb.go index 58f3fe00..8f28d486 100644 --- a/component/mmdb/mmdb.go +++ b/component/mmdb/mmdb.go @@ -2,6 +2,9 @@ package mmdb import ( "github.com/oschwald/geoip2-golang" + "io" + "net/http" + "os" "sync" C "github.com/Dreamacro/clash/constant" @@ -42,3 +45,20 @@ func Instance() *geoip2.Reader { return mmdb } + +func DownloadMMDB(path string) (err error) { + resp, err := http.Get(C.MmdbUrl) + if err != nil { + return + } + defer resp.Body.Close() + + f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644) + if err != nil { + return err + } + defer f.Close() + _, err = io.Copy(f, resp.Body) + + return err +} diff --git a/component/process/find_process_mode.go b/component/process/find_process_mode.go new file mode 100644 index 00000000..06618cef --- /dev/null +++ b/component/process/find_process_mode.go @@ -0,0 +1,57 @@ +package process + +import ( + "encoding/json" + "errors" + "strings" +) + +const ( + FindProcessAlways = "always" + FindProcessStrict = "strict" + FindProcessOff = "off" +) + +var ( + validModes = map[string]struct{}{ + FindProcessAlways: {}, + FindProcessOff: {}, + FindProcessStrict: {}, + } +) + +type FindProcessMode string + +func (m FindProcessMode) Always() bool { + return m == FindProcessAlways +} + +func (m FindProcessMode) Off() bool { + return m == FindProcessOff +} + +func (m *FindProcessMode) UnmarshalYAML(unmarshal func(any) error) error { + var tp string + if err := unmarshal(&tp); err != nil { + return err + } + return m.Set(tp) +} + +func (m *FindProcessMode) UnmarshalJSON(data []byte) error { + var tp string + if err := json.Unmarshal(data, &tp); err != nil { + return err + } + return m.Set(tp) +} + +func (m *FindProcessMode) Set(value string) error { + mode := strings.ToLower(value) + _, exist := validModes[mode] + if !exist { + return errors.New("invalid find process mode") + } + *m = FindProcessMode(mode) + return nil +} diff --git a/config/config.go b/config/config.go index da05877f..e2250e8a 100644 --- a/config/config.go +++ b/config/config.go @@ -4,6 +4,7 @@ import ( "container/list" "errors" "fmt" + P "github.com/Dreamacro/clash/component/process" "net" "net/netip" "net/url" @@ -42,18 +43,19 @@ import ( type General struct { Inbound Controller - Mode T.TunnelMode `json:"mode"` - UnifiedDelay bool - LogLevel log.LogLevel `json:"log-level"` - IPv6 bool `json:"ipv6"` - Interface string `json:"interface-name"` - RoutingMark int `json:"-"` - GeodataMode bool `json:"geodata-mode"` - GeodataLoader string `json:"geodata-loader"` - TCPConcurrent bool `json:"tcp-concurrent"` - EnableProcess bool `json:"enable-process"` - Sniffing bool `json:"sniffing"` - EBpf EBpf `json:"-"` + Mode T.TunnelMode `json:"mode"` + UnifiedDelay bool + LogLevel log.LogLevel `json:"log-level"` + IPv6 bool `json:"ipv6"` + Interface string `json:"interface-name"` + RoutingMark int `json:"-"` + GeodataMode bool `json:"geodata-mode"` + GeodataLoader string `json:"geodata-loader"` + TCPConcurrent bool `json:"tcp-concurrent"` + EnableProcess bool `json:"enable-process"` + FindProcessMode P.FindProcessMode `json:"find-process-mode"` + Sniffing bool `json:"sniffing"` + EBpf EBpf `json:"-"` } // Inbound config @@ -226,32 +228,33 @@ type RawTuicServer struct { } type RawConfig struct { - Port int `yaml:"port"` - SocksPort int `yaml:"socks-port"` - RedirPort int `yaml:"redir-port"` - TProxyPort int `yaml:"tproxy-port"` - MixedPort int `yaml:"mixed-port"` - ShadowSocksConfig string `yaml:"ss-config"` - VmessConfig string `yaml:"vmess-config"` - InboundTfo bool `yaml:"inbound-tfo"` - Authentication []string `yaml:"authentication"` - AllowLan bool `yaml:"allow-lan"` - BindAddress string `yaml:"bind-address"` - Mode T.TunnelMode `yaml:"mode"` - UnifiedDelay bool `yaml:"unified-delay"` - LogLevel log.LogLevel `yaml:"log-level"` - IPv6 bool `yaml:"ipv6"` - ExternalController string `yaml:"external-controller"` - ExternalControllerTLS string `yaml:"external-controller-tls"` - ExternalUI string `yaml:"external-ui"` - Secret string `yaml:"secret"` - Interface string `yaml:"interface-name"` - RoutingMark int `yaml:"routing-mark"` - Tunnels []LC.Tunnel `yaml:"tunnels"` - GeodataMode bool `yaml:"geodata-mode"` - GeodataLoader string `yaml:"geodata-loader"` - TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"` - EnableProcess bool `yaml:"enable-process" json:"enable-process"` + Port int `yaml:"port"` + SocksPort int `yaml:"socks-port"` + RedirPort int `yaml:"redir-port"` + TProxyPort int `yaml:"tproxy-port"` + MixedPort int `yaml:"mixed-port"` + ShadowSocksConfig string `yaml:"ss-config"` + VmessConfig string `yaml:"vmess-config"` + InboundTfo bool `yaml:"inbound-tfo"` + Authentication []string `yaml:"authentication"` + AllowLan bool `yaml:"allow-lan"` + BindAddress string `yaml:"bind-address"` + Mode T.TunnelMode `yaml:"mode"` + UnifiedDelay bool `yaml:"unified-delay"` + LogLevel log.LogLevel `yaml:"log-level"` + IPv6 bool `yaml:"ipv6"` + ExternalController string `yaml:"external-controller"` + ExternalControllerTLS string `yaml:"external-controller-tls"` + ExternalUI string `yaml:"external-ui"` + Secret string `yaml:"secret"` + Interface string `yaml:"interface-name"` + RoutingMark int `yaml:"routing-mark"` + Tunnels []LC.Tunnel `yaml:"tunnels"` + GeodataMode bool `yaml:"geodata-mode"` + GeodataLoader string `yaml:"geodata-loader"` + TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"` + EnableProcess bool `yaml:"enable-process" json:"enable-process"` + FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"` Sniffer RawSniffer `yaml:"sniffer"` ProxyProvider map[string]map[string]any `yaml:"proxy-providers"` @@ -314,21 +317,22 @@ func Parse(buf []byte) (*Config, error) { func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { // config with default value rawCfg := &RawConfig{ - AllowLan: false, - BindAddress: "*", - IPv6: true, - Mode: T.Rule, - GeodataMode: C.GeodataMode, - GeodataLoader: "memconservative", - UnifiedDelay: false, - Authentication: []string{}, - LogLevel: log.INFO, - Hosts: map[string]string{}, - Rule: []string{}, - Proxy: []map[string]any{}, - ProxyGroup: []map[string]any{}, - TCPConcurrent: false, - EnableProcess: false, + AllowLan: false, + BindAddress: "*", + IPv6: true, + Mode: T.Rule, + GeodataMode: C.GeodataMode, + GeodataLoader: "memconservative", + UnifiedDelay: false, + Authentication: []string{}, + LogLevel: log.INFO, + Hosts: map[string]string{}, + Rule: []string{}, + Proxy: []map[string]any{}, + ProxyGroup: []map[string]any{}, + TCPConcurrent: false, + EnableProcess: false, + FindProcessMode: P.FindProcessStrict, Tun: RawTun{ Enable: false, Device: "", @@ -536,17 +540,18 @@ func parseGeneral(cfg *RawConfig) (*General, error) { Secret: cfg.Secret, ExternalControllerTLS: cfg.ExternalControllerTLS, }, - UnifiedDelay: cfg.UnifiedDelay, - Mode: cfg.Mode, - LogLevel: cfg.LogLevel, - IPv6: cfg.IPv6, - Interface: cfg.Interface, - RoutingMark: cfg.RoutingMark, - GeodataMode: cfg.GeodataMode, - GeodataLoader: cfg.GeodataLoader, - TCPConcurrent: cfg.TCPConcurrent, - EnableProcess: cfg.EnableProcess, - EBpf: cfg.EBpf, + UnifiedDelay: cfg.UnifiedDelay, + Mode: cfg.Mode, + LogLevel: cfg.LogLevel, + IPv6: cfg.IPv6, + Interface: cfg.Interface, + RoutingMark: cfg.RoutingMark, + GeodataMode: cfg.GeodataMode, + GeodataLoader: cfg.GeodataLoader, + TCPConcurrent: cfg.TCPConcurrent, + EnableProcess: cfg.EnableProcess, + FindProcessMode: cfg.FindProcessMode, + EBpf: cfg.EBpf, }, nil } diff --git a/config/initial.go b/config/initial.go index d819168f..0921040d 100644 --- a/config/initial.go +++ b/config/initial.go @@ -3,92 +3,12 @@ package config import ( "fmt" "github.com/Dreamacro/clash/component/geodata" - "github.com/Dreamacro/clash/component/mmdb" - "io" - "net/http" "os" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" ) -func downloadMMDB(path string) (err error) { - resp, err := http.Get(C.MmdbUrl) - if err != nil { - return - } - defer resp.Body.Close() - - f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644) - if err != nil { - return err - } - defer f.Close() - _, err = io.Copy(f, resp.Body) - - return err -} - -func downloadGeoIP(path string) (err error) { - resp, err := http.Get(C.GeoIpUrl) - if err != nil { - return - } - defer resp.Body.Close() - - f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644) - if err != nil { - return err - } - defer f.Close() - _, err = io.Copy(f, resp.Body) - - return err -} - -func initGeoIP() error { - if C.GeodataMode { - if _, err := os.Stat(C.Path.GeoIP()); os.IsNotExist(err) { - log.Infoln("Can't find GeoIP.dat, start download") - if err := downloadGeoIP(C.Path.GeoIP()); err != nil { - return fmt.Errorf("can't download GeoIP.dat: %s", err.Error()) - } - log.Infoln("Download GeoIP.dat finish") - } - - if err := geodata.Verify(C.GeoipName); err != nil { - log.Warnln("GeoIP.dat invalid, remove and download: %s", err) - if err := os.Remove(C.Path.GeoIP()); err != nil { - return fmt.Errorf("can't remove invalid GeoIP.dat: %s", err.Error()) - } - if err := downloadGeoIP(C.Path.GeoIP()); err != nil { - return fmt.Errorf("can't download GeoIP.dat: %s", err.Error()) - } - } - return nil - } - - if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) { - log.Infoln("Can't find MMDB, start download") - if err := downloadMMDB(C.Path.MMDB()); err != nil { - return fmt.Errorf("can't download MMDB: %s", err.Error()) - } - } - - if !mmdb.Verify() { - log.Warnln("MMDB invalid, remove and download") - if err := os.Remove(C.Path.MMDB()); err != nil { - return fmt.Errorf("can't remove invalid MMDB: %s", err.Error()) - } - - if err := downloadMMDB(C.Path.MMDB()); err != nil { - return fmt.Errorf("can't download MMDB: %s", err.Error()) - } - } - - return nil -} - // Init prepare necessary files func Init(dir string) error { // initial homedir @@ -122,7 +42,7 @@ func Init(dir string) error { C.GeoSiteUrl = rawCfg.GeoXUrl.GeoSite C.MmdbUrl = rawCfg.GeoXUrl.Mmdb // initial GeoIP - if err := initGeoIP(); err != nil { + if err := geodata.InitGeoIP(); err != nil { return fmt.Errorf("can't initial GeoIP: %w", err) } diff --git a/dns/util.go b/dns/util.go index 8259f22e..e53abab0 100644 --- a/dns/util.go +++ b/dns/util.go @@ -29,9 +29,11 @@ const ( func putMsgToCache(c *cache.LruCache[string, *D.Msg], key string, msg *D.Msg) { // skip dns cache for acme challenge - if q := msg.Question[0]; q.Qtype == D.TypeTXT && strings.HasPrefix(q.Name, "_acme-challenge") { - log.Debugln("[DNS] dns cache ignored because of acme challenge for: %s", q.Name) - return + if len(msg.Question) != 0 { + if q := msg.Question[0]; q.Qtype == D.TypeTXT && strings.HasPrefix(q.Name, "_acme-challenge") { + log.Debugln("[DNS] dns cache ignored because of acme challenge for: %s", q.Name) + return + } } var ttl uint32 switch { diff --git a/docs/config.yaml b/docs/config.yaml index b6fc74f9..27be2ce7 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -258,6 +258,17 @@ proxies: # headers: # custom: value + - name: "ss4" + type: ss + server: server + port: 443 + cipher: chacha20-ietf-poly1305 + password: "password" + plugin: shadow-tls + plugin-opts: + host: "cloud.tencent.com" + password: "shadow_tls_password" + # vmess # cipher支持 auto/aes-128-gcm/chacha20-poly1305/none - name: "vmess" diff --git a/hub/executor/executor.go b/hub/executor/executor.go index eb4436fb..55786864 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -309,7 +309,7 @@ func updateTunnels(tunnels []LC.Tunnel) { func updateGeneral(general *config.General, force bool) { tunnel.SetMode(general.Mode) - tunnel.SetAlwaysFindProcess(general.EnableProcess) + tunnel.SetFindProcessMode(general.EnableProcess, general.FindProcessMode) dialer.DisableIPv6 = !general.IPv6 if !dialer.DisableIPv6 { log.Infoln("Use IPv6") diff --git a/rules/common/base.go b/rules/common/base.go index abc2b434..d912107c 100644 --- a/rules/common/base.go +++ b/rules/common/base.go @@ -6,7 +6,6 @@ import ( var ( errPayload = errors.New("payloadRule error") - initFlag bool noResolve = "no-resolve" ) diff --git a/rules/common/geoip.go b/rules/common/geoip.go index 72e77045..0c134c63 100644 --- a/rules/common/geoip.go +++ b/rules/common/geoip.go @@ -71,6 +71,11 @@ func (g *GEOIP) GetRecodeSize() int { } func NewGEOIP(country string, adapter string, noResolveIP bool) (*GEOIP, error) { + if err := geodata.InitGeoIP(); err != nil { + log.Errorln("can't initial GeoIP: %s", err) + return nil, err + } + if !C.GeodataMode || strings.EqualFold(country, "LAN") { geoip := &GEOIP{ Base: &Base{}, diff --git a/rules/common/geosite.go b/rules/common/geosite.go index e5a0b9f7..865b0b1b 100644 --- a/rules/common/geosite.go +++ b/rules/common/geosite.go @@ -50,12 +50,9 @@ func (gs *GEOSITE) GetRecodeSize() int { } func NewGEOSITE(country string, adapter string) (*GEOSITE, error) { - if !initFlag { - if err := geodata.InitGeoSite(); err != nil { - log.Errorln("can't initial GeoSite: %s", err) - return nil, err - } - initFlag = true + if err := geodata.InitGeoSite(); err != nil { + log.Errorln("can't initial GeoSite: %s", err) + return nil, err } matcher, size, err := geodata.LoadGeoSiteMatcher(country) @@ -76,4 +73,4 @@ func NewGEOSITE(country string, adapter string) (*GEOSITE, error) { return geoSite, nil } -//var _ C.Rule = (*GEOSITE)(nil) +var _ C.Rule = (*GEOSITE)(nil) diff --git a/rules/provider/classical_strategy.go b/rules/provider/classical_strategy.go index 12623271..73426e85 100644 --- a/rules/provider/classical_strategy.go +++ b/rules/provider/classical_strategy.go @@ -8,10 +8,11 @@ import ( ) type classicalStrategy struct { - rules []C.Rule - count int - shouldResolveIP bool - parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) + rules []C.Rule + count int + shouldResolveIP bool + shouldFindProcess bool + parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) } func (c *classicalStrategy) Match(metadata *C.Metadata) bool { @@ -32,6 +33,10 @@ func (c *classicalStrategy) ShouldResolveIP() bool { return c.shouldResolveIP } +func (c *classicalStrategy) ShouldFindProcess() bool { + return c.shouldFindProcess +} + func (c *classicalStrategy) OnUpdate(rules []string) { var classicalRules []C.Rule shouldResolveIP := false @@ -45,6 +50,10 @@ func (c *classicalStrategy) OnUpdate(rules []string) { shouldResolveIP = r.ShouldResolveIP() } + if !c.shouldFindProcess { + c.shouldFindProcess = r.ShouldFindProcess() + } + classicalRules = append(classicalRules, r) } } diff --git a/transport/gun/gun_xtls.go b/transport/gun/gun_xtls.go deleted file mode 100644 index 0d110727..00000000 --- a/transport/gun/gun_xtls.go +++ /dev/null @@ -1,60 +0,0 @@ -// Modified from: https://github.com/Qv2ray/gun-lite -// License: MIT - -package gun - -import ( - "crypto/tls" - "fmt" - "net" - - xtls "github.com/xtls/go" - "golang.org/x/net/http2" -) - -func NewHTTP2XTLSClient(dialFn DialFn, tlsConfig *tls.Config) *TransportWrap { - wrap := TransportWrap{} - dialFunc := func(network, addr string, cfg *tls.Config) (net.Conn, error) { - pconn, err := dialFn(network, addr) - if err != nil { - return nil, err - } - - wrap.remoteAddr = pconn.RemoteAddr() - xtlsConfig := &xtls.Config{ - InsecureSkipVerify: cfg.InsecureSkipVerify, - ServerName: cfg.ServerName, - } - - cn := xtls.Client(pconn, xtlsConfig) - if err := cn.Handshake(); err != nil { - pconn.Close() - return nil, err - } - state := cn.ConnectionState() - if p := state.NegotiatedProtocol; p != http2.NextProtoTLS { - cn.Close() - return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS) - } - return cn, nil - } - - wrap.Transport = &http2.Transport{ - DialTLS: dialFunc, - TLSClientConfig: tlsConfig, - AllowHTTP: false, - DisableCompression: true, - PingTimeout: 0, - } - - return &wrap -} - -func StreamGunWithXTLSConn(conn net.Conn, tlsConfig *tls.Config, cfg *Config) (net.Conn, error) { - dialFn := func(network, addr string) (net.Conn, error) { - return conn, nil - } - - transport := NewHTTP2XTLSClient(dialFn, tlsConfig) - return StreamGunWithTransport(transport, cfg) -} diff --git a/transport/shadowtls/shadowtls.go b/transport/shadowtls/shadowtls.go new file mode 100644 index 00000000..86a23779 --- /dev/null +++ b/transport/shadowtls/shadowtls.go @@ -0,0 +1,152 @@ +package shadowtls + +import ( + "context" + "crypto/hmac" + "crypto/sha1" + "crypto/tls" + "encoding/binary" + "fmt" + "hash" + "io" + "net" + + "github.com/Dreamacro/clash/common/pool" + C "github.com/Dreamacro/clash/constant" +) + +const ( + chunkSize = 1 << 13 + Mode string = "shadow-tls" + hashLen int = 8 + tlsHeaderLen int = 5 +) + +var ( + DefaultALPN = []string{"h2", "http/1.1"} +) + +// ShadowTLS is shadow-tls implementation +type ShadowTLS struct { + net.Conn + password []byte + remain int + firstRequest bool + tlsConfig *tls.Config +} + +type HashedConn struct { + net.Conn + hasher hash.Hash +} + +func newHashedStream(conn net.Conn, password []byte) HashedConn { + return HashedConn{ + Conn: conn, + hasher: hmac.New(sha1.New, password), + } +} + +func (h HashedConn) Read(b []byte) (n int, err error) { + n, err = h.Conn.Read(b) + h.hasher.Write(b[:n]) + return +} + +func (s *ShadowTLS) read(b []byte) (int, error) { + buf := pool.Get(tlsHeaderLen) + _, err := io.ReadFull(s.Conn, buf) + if err != nil { + return 0, fmt.Errorf("shadowtls read failed %w", err) + } + if buf[0] != 0x17 || buf[1] != 0x3 || buf[2] != 0x3 { + return 0, fmt.Errorf("invalid shadowtls header %v", buf) + } + length := int(binary.BigEndian.Uint16(buf[3:])) + pool.Put(buf) + + if length > len(b) { + n, err := s.Conn.Read(b) + if err != nil { + return n, err + } + s.remain = length - n + return n, nil + } + + return io.ReadFull(s.Conn, b[:length]) +} + +func (s *ShadowTLS) Read(b []byte) (int, error) { + if s.remain > 0 { + length := s.remain + if length > len(b) { + length = len(b) + } + + n, err := io.ReadFull(s.Conn, b[:length]) + if err != nil { + return n, fmt.Errorf("shadowtls Read failed with %w", err) + } + s.remain -= n + return n, nil + } + + return s.read(b) +} + +func (s *ShadowTLS) Write(b []byte) (int, error) { + length := len(b) + for i := 0; i < length; i += chunkSize { + end := i + chunkSize + if end > length { + end = length + } + + n, err := s.write(b[i:end]) + if err != nil { + return n, fmt.Errorf("shadowtls Write failed with %w, i=%d, end=%d, n=%d", err, i, end, n) + } + } + return length, nil +} + +func (s *ShadowTLS) write(b []byte) (int, error) { + var hashVal []byte + if s.firstRequest { + hashedConn := newHashedStream(s.Conn, s.password) + tlsConn := tls.Client(hashedConn, s.tlsConfig) + // fix tls handshake not timeout + ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout) + defer cancel() + if err := tlsConn.HandshakeContext(ctx); err != nil { + return 0, fmt.Errorf("tls connect failed with %w", err) + } + hashVal = hashedConn.hasher.Sum(nil)[:hashLen] + s.firstRequest = false + } + + buf := pool.GetBuffer() + defer pool.PutBuffer(buf) + buf.Write([]byte{0x17, 0x03, 0x03}) + binary.Write(buf, binary.BigEndian, uint16(len(b)+len(hashVal))) + buf.Write(hashVal) + buf.Write(b) + _, err := s.Conn.Write(buf.Bytes()) + if err != nil { + // return 0 because errors occur here make the + // whole situation irrecoverable + return 0, err + } + return len(b), nil +} + +// NewShadowTLS return a ShadowTLS +func NewShadowTLS(conn net.Conn, password string, tlsConfig *tls.Config) net.Conn { + return &ShadowTLS{ + Conn: conn, + password: []byte(password), + firstRequest: true, + tlsConfig: tlsConfig, + } +} diff --git a/transport/simple-obfs/tls.go b/transport/simple-obfs/tls.go index 1c609c15..fed8a483 100644 --- a/transport/simple-obfs/tls.go +++ b/transport/simple-obfs/tls.go @@ -109,7 +109,12 @@ func (to *TLSObfs) write(b []byte) (int, error) { binary.Write(buf, binary.BigEndian, uint16(len(b))) buf.Write(b) _, err := to.Conn.Write(buf.Bytes()) - return len(b), err + if err != nil { + // return 0 because errors occur here make the + // whole situation irrecoverable + return 0, err + } + return len(b), nil } // NewTLSObfs return a SimpleObfs diff --git a/transport/trojan/trojan.go b/transport/trojan/trojan.go index 937b5f91..86de2f65 100644 --- a/transport/trojan/trojan.go +++ b/transport/trojan/trojan.go @@ -8,18 +8,17 @@ import ( "encoding/hex" "errors" "fmt" - tlsC "github.com/Dreamacro/clash/component/tls" "io" "net" "net/http" "sync" "github.com/Dreamacro/clash/common/pool" + tlsC "github.com/Dreamacro/clash/component/tls" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/transport/socks5" "github.com/Dreamacro/clash/transport/vless" "github.com/Dreamacro/clash/transport/vmess" - xtls "github.com/xtls/go" ) @@ -117,9 +116,6 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) { } tlsConn := tls.Client(conn, tlsConfig) - if err := tlsConn.Handshake(); err != nil { - return nil, err - } // fix tls handshake not timeout ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout) defer cancel() diff --git a/transport/tuic/congestion/bbr_sender.go b/transport/tuic/congestion/bbr_sender.go index d4ba20d1..1706956b 100644 --- a/transport/tuic/congestion/bbr_sender.go +++ b/transport/tuic/congestion/bbr_sender.go @@ -12,12 +12,14 @@ import ( "github.com/metacubex/quic-go/congestion" ) -const DefaultTCPMSS congestion.ByteCount = 1460 -const DefaultBBRMaxCongestionWindow congestion.ByteCount = 2000 * DefaultTCPMSS -const InitialCongestionWindow congestion.ByteCount = 32 * DefaultTCPMSS -const MinInitialPacketSize = 1200 -const InitialPacketSizeIPv4 = 1252 -const InitialPacketSizeIPv6 = 1232 +const ( + MaxDatagramSize = 1252 + DefaultBBRMaxCongestionWindow congestion.ByteCount = 2000 * MaxDatagramSize + InitialCongestionWindow congestion.ByteCount = 10 * MaxDatagramSize + MinInitialPacketSize = 1200 + InitialPacketSizeIPv4 = 1252 + InitialPacketSizeIPv6 = 1232 +) func GetMaxPacketSize(addr net.Addr) congestion.ByteCount { maxSize := congestion.ByteCount(MinInitialPacketSize) @@ -33,17 +35,32 @@ func GetMaxPacketSize(addr net.Addr) congestion.ByteCount { return maxSize } +func GetMaxOutgoingPacketSize(addr net.Addr) congestion.ByteCount { + maxSize := congestion.ByteCount(MinInitialPacketSize) + // If this is not a UDP address, we don't know anything about the MTU. + // Use the minimum size of an Initial packet as the max packet size. + if udpAddr, ok := addr.(*net.UDPAddr); ok { + + if udpAddr.IP.To4() != nil { + //The maximum packet size of any QUIC packet over IPv4. 1500(Ethernet) - 20(IPv4 header) - 8(UDP header) = 1472. + maxSize = congestion.ByteCount(1472) + } else { + // The maximum outgoing packet size allowed. + // The maximum packet size of any QUIC packet over IPv6, based on ethernet's max + // size, minus the IP and UDP headers. IPv6 has a 40 byte header, UDP adds an + // additional 8 bytes. This is a total overhead of 48 bytes. Ethernet's + // max packet size is 1500 bytes, 1500 - 48 = 1452. + maxSize = congestion.ByteCount(1452) + } + } + return maxSize +} + var ( - // The maximum outgoing packet size allowed. - // The maximum packet size of any QUIC packet over IPv6, based on ethernet's max - // size, minus the IP and UDP headers. IPv6 has a 40 byte header, UDP adds an - // additional 8 bytes. This is a total overhead of 48 bytes. Ethernet's - // max packet size is 1500 bytes, 1500 - 48 = 1452. - MaxOutgoingPacketSize = congestion.ByteCount(1452) // Default maximum packet size used in the Linux TCP implementation. // Used in QUIC for congestion window computations in bytes. - MaxSegmentSize = DefaultTCPMSS + MaxSegmentSize = MaxDatagramSize // Default initial rtt used before any samples are received. InitialRtt = 100 * time.Millisecond @@ -51,10 +68,10 @@ var ( // Constants based on TCP defaults. // The minimum CWND to ensure delayed acks don't reduce bandwidth measurements. // Does not inflate the pacing rate. - DefaultMinimumCongestionWindow = 4 * DefaultTCPMSS + DefaultMinimumCongestionWindow = 4 * MaxDatagramSize // The gain used for the STARTUP, equal to 2/ln(2). - DefaultHighGain = 2.885 + DefaultHighGain = 2.89 // The gain used in STARTUP after loss has been detected. // 1.5 is enough to allow for 25% exogenous loss and still observe a 25% growth @@ -249,11 +266,14 @@ type bbrSender struct { // Latched value of --quic_always_get_bw_sample_when_acked. alwaysGetBwSampleWhenAcked bool - pacer *pacer + pacer *pacer + maxDatagramSize congestion.ByteCount + + MaxOutgoingPacketSize congestion.ByteCount } -func NewBBRSender(clock Clock, initialMaxDatagramSize, initialCongestionWindow, maxCongestionWindow congestion.ByteCount) *bbrSender { +func NewBBRSender(clock Clock, initialMaxDatagramSize, initialCongestionWindow, initialMaxOutgoingPacketSize, maxCongestionWindow congestion.ByteCount) *bbrSender { b := &bbrSender{ mode: STARTUP, clock: clock, @@ -263,7 +283,7 @@ func NewBBRSender(clock Clock, initialMaxDatagramSize, initialCongestionWindow, congestionWindow: initialCongestionWindow, initialCongestionWindow: initialCongestionWindow, maxCongestionWindow: maxCongestionWindow, - minCongestionWindow: DefaultMinimumCongestionWindow, + minCongestionWindow: congestion.ByteCount(DefaultMinimumCongestionWindow), highGain: DefaultHighGain, highCwndGain: DefaultHighGain, drainGain: 1.0 / DefaultHighGain, @@ -274,6 +294,7 @@ func NewBBRSender(clock Clock, initialMaxDatagramSize, initialCongestionWindow, recoveryState: NOT_IN_RECOVERY, recoveryWindow: maxCongestionWindow, minRttSinceLastProbeRtt: InfiniteRTT, + MaxOutgoingPacketSize: initialMaxOutgoingPacketSize, maxDatagramSize: initialMaxDatagramSize, } b.pacer = newPacer(b.BandwidthEstimate) @@ -790,7 +811,7 @@ func (b *bbrSender) MaybeEnterOrExitProbeRtt(now time.Time, isRoundStart, minRtt // PROBE_RTT. The CWND during PROBE_RTT is kMinimumCongestionWindow, but // we allow an extra packet since QUIC checks CWND before sending a // packet. - if b.GetBytesInFlight() < b.ProbeRttCongestionWindow()+MaxOutgoingPacketSize { + if b.GetBytesInFlight() < b.ProbeRttCongestionWindow()+b.MaxOutgoingPacketSize { b.exitProbeRttAt = now.Add(ProbeRttTime) b.probeRttRoundPassed = false } @@ -924,7 +945,7 @@ func (b *bbrSender) CalculateRecoveryWindow(ackedBytes, lostBytes congestion.Byt if b.recoveryWindow >= lostBytes { b.recoveryWindow -= lostBytes } else { - b.recoveryWindow = MaxSegmentSize + b.recoveryWindow = congestion.ByteCount(MaxSegmentSize) } // In CONSERVATION mode, just subtracting losses is sufficient. In GROWTH, // release additional |bytes_acked| to achieve a slow-start-like behavior. diff --git a/transport/tuic/conn.go b/transport/tuic/conn.go index 81e8c40e..8e6722c0 100644 --- a/transport/tuic/conn.go +++ b/transport/tuic/conn.go @@ -45,6 +45,7 @@ func SetCongestionController(quicConn quic.Connection, cc string) { congestion.NewBBRSender( congestion.DefaultClock{}, congestion.GetMaxPacketSize(quicConn.RemoteAddr()), + congestion.GetMaxOutgoingPacketSize(quicConn.RemoteAddr()), congestion.InitialCongestionWindow, congestion.DefaultBBRMaxCongestionWindow, ), diff --git a/transport/vless/conn.go b/transport/vless/conn.go index e6e6e34c..5ee69611 100644 --- a/transport/vless/conn.go +++ b/transport/vless/conn.go @@ -51,17 +51,20 @@ func (vc *Conn) sendRequest() error { buf.WriteByte(0) // addon data length. 0 means no addon data } - // command - if vc.dst.UDP { - buf.WriteByte(CommandUDP) + if vc.dst.Mux { + buf.WriteByte(CommandMux) } else { - buf.WriteByte(CommandTCP) - } + if vc.dst.UDP { + buf.WriteByte(CommandUDP) + } else { + buf.WriteByte(CommandTCP) + } - // Port AddrType Addr - binary.Write(buf, binary.BigEndian, uint16(vc.dst.Port)) - buf.WriteByte(vc.dst.AddrType) - buf.Write(vc.dst.Addr) + // Port AddrType Addr + binary.Write(buf, binary.BigEndian, vc.dst.Port) + buf.WriteByte(vc.dst.AddrType) + buf.Write(vc.dst.Addr) + } _, err := vc.Conn.Write(buf.Bytes()) return err diff --git a/transport/vless/vless.go b/transport/vless/vless.go index 5afe2c3e..4b101703 100644 --- a/transport/vless/vless.go +++ b/transport/vless/vless.go @@ -1,9 +1,10 @@ package vless import ( - "github.com/Dreamacro/clash/common/utils" "net" + "github.com/Dreamacro/clash/common/utils" + "github.com/gofrs/uuid" ) @@ -19,6 +20,7 @@ const ( const ( CommandTCP byte = 1 CommandUDP byte = 2 + CommandMux byte = 3 ) // Addr types @@ -33,7 +35,8 @@ type DstAddr struct { UDP bool AddrType byte Addr []byte - Port uint + Port uint16 + Mux bool // currently used for XUDP only } // Client is vless connection generator diff --git a/transport/vless/xtls.go b/transport/vless/xtls.go index 8ef4b44e..ab8248af 100644 --- a/transport/vless/xtls.go +++ b/transport/vless/xtls.go @@ -12,7 +12,7 @@ import ( type XTLSConfig struct { Host string SkipCertVerify bool - FingerPrint string + Fingerprint string NextProtos []string } @@ -22,11 +22,11 @@ func StreamXTLSConn(conn net.Conn, cfg *XTLSConfig) (net.Conn, error) { InsecureSkipVerify: cfg.SkipCertVerify, NextProtos: cfg.NextProtos, } - if len(cfg.FingerPrint) == 0 { + if len(cfg.Fingerprint) == 0 { xtlsConfig = tlsC.GetGlobalFingerprintXTLCConfig(xtlsConfig) } else { var err error - if xtlsConfig, err = tlsC.GetSpecifiedFingerprintXTLSConfig(xtlsConfig, cfg.FingerPrint); err != nil { + if xtlsConfig, err = tlsC.GetSpecifiedFingerprintXTLSConfig(xtlsConfig, cfg.Fingerprint); err != nil { return nil, err } } diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 8f0ef8f6..c5534062 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -43,7 +43,7 @@ var ( // default timeout for UDP session udpTimeout = 60 * time.Second - alwaysFindProcess = false + findProcessMode P.FindProcessMode fakeIPRange netip.Prefix ) @@ -146,9 +146,14 @@ func SetMode(m TunnelMode) { mode = m } -// SetAlwaysFindProcess set always find process info, may be increase many memory -func SetAlwaysFindProcess(findProcess bool) { - alwaysFindProcess = findProcess +// SetFindProcessMode replace SetAlwaysFindProcess +// always find process info if legacyAlways = true or mode.Always() = true, may be increase many memory +func SetFindProcessMode(legacyAlways bool, mode P.FindProcessMode) { + if legacyAlways { + findProcessMode = P.FindProcessAlways + } else { + findProcessMode = mode + } } // processUDP starts a loop to handle udp packet @@ -463,7 +468,7 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { }() } - if !processFound && (alwaysFindProcess || rule.ShouldFindProcess()) { + if !findProcessMode.Off() && !processFound && (findProcessMode.Always() || rule.ShouldFindProcess()) { srcPort, err := strconv.ParseUint(metadata.SrcPort, 10, 16) uid, path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, int(srcPort)) if err != nil {