Merge branch 'Beta' into Meta

This commit is contained in:
metacubex 2023-01-12 01:33:56 +08:00
commit b9ffc82e53
39 changed files with 718 additions and 353 deletions

View file

@ -1,4 +1,4 @@
NAME=Clash.Meta
NAME=clash.meta
BINDIR=bin
BRANCH=$(shell git branch --show-current)
ifeq ($(BRANCH),Alpha)

View file

@ -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
}

View file

@ -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{

View file

@ -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

View file

@ -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)
}

View file

@ -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,

View file

@ -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

View file

@ -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,

View file

@ -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) {

View file

@ -191,6 +191,7 @@ func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Re
},
"",
"",
"",
providers,
}),
}

View file

@ -100,6 +100,7 @@ func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider)
},
option.Filter,
option.ExcludeFilter,
option.ExcludeType,
providers,
}),
selected: "COMPATIBLE",

View file

@ -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),

View file

@ -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)
}

View file

@ -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

View file

@ -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)

View file

@ -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
}

View file

@ -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"

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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 {

View file

@ -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"

View file

@ -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")

View file

@ -6,7 +6,6 @@ import (
var (
errPayload = errors.New("payloadRule error")
initFlag bool
noResolve = "no-resolve"
)

View file

@ -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{},

View file

@ -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)

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -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,
}
}

View file

@ -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

View file

@ -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()

View file

@ -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.

View file

@ -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,
),

View file

@ -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

View file

@ -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

View file

@ -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
}
}

View file

@ -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 {