From c57d92d7c124f07e87c0b04c574872ab4289d6e0 Mon Sep 17 00:00:00 2001 From: gVisor bot Date: Fri, 4 Feb 2022 23:33:36 +0800 Subject: [PATCH] [Feat] support trojan xtls change geodataloader mode as memconservative --- .github/workflows/dev.yml | 44 ---------------------- adapter/outbound/trojan.go | 28 +++++++++++--- adapter/outbound/util.go | 23 ++++++++++++ component/geodata/utils.go | 29 ++++++++++++++- config/config.go | 3 ++ rule/common/geoip.go | 27 +++----------- rule/common/geosite.go | 1 + transport/trojan/trojan.go | 75 ++++++++++++++++++++++++++++---------- 8 files changed, 138 insertions(+), 92 deletions(-) delete mode 100644 .github/workflows/dev.yml diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml deleted file mode 100644 index 1c0b0a62..00000000 --- a/.github/workflows/dev.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Dev -on: [push] -jobs: - dev-build: - if: ${{ !contains(github.event.head_commit.message, '[Skip CI]') }} - runs-on: ubuntu-latest - steps: - - name: Get latest go version - id: version - run: | - echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') - - name: Setup Go - uses: actions/setup-go@v2 - with: - go-version: ${{ steps.version.outputs.go_version }} - - - name: Check out code into the Go module directory - uses: actions/checkout@v2 - - - name: Cache go module - uses: actions/cache@v2 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- -# - name: Get dependencies, run test -# run: | -# go test ./... - - name: Build - if: success() - env: - NAME: Clash.Meta - BINDIR: bin - run: make -j releases - - - name: Upload Dev - uses: softprops/action-gh-release@v1 - if: ${{ env.GIT_BRANCH != 'Meta' && success() }} - with: - tag_name: develop - files: bin/* - prerelease: true - diff --git a/adapter/outbound/trojan.go b/adapter/outbound/trojan.go index 35dbea1e..668cbb2d 100644 --- a/adapter/outbound/trojan.go +++ b/adapter/outbound/trojan.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "fmt" + xtls "github.com/xtls/go" "net" "net/http" "strconv" @@ -33,6 +34,7 @@ type TrojanOption struct { Server string `proxy:"server"` Port int `proxy:"port"` Password string `proxy:"password"` + Flow string `proxy:"flow,omitempty"` ALPN []string `proxy:"alpn,omitempty"` SNI string `proxy:"sni,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` @@ -81,8 +83,19 @@ func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) if err != nil { return nil, fmt.Errorf("%s connect error: %w", t.addr, err) } - - err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)) + var tc trojan.Command + if xtlsConn, ok := c.(*xtls.Conn); ok { + xtlsConn.RPRX = true + if t.instance.GetFlow() == trojan.XRD { + xtlsConn.DirectMode = true + tc = trojan.CommandXRD + } else { + tc = trojan.CommandXRO + } + } else { + tc = trojan.CommandTCP + } + err = t.instance.WriteHeader(c, tc, serializesSocksAddr(metadata)) return c, err } @@ -156,10 +169,13 @@ func NewTrojan(option TrojanOption) (*Trojan, error) { addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) tOption := &trojan.Option{ - Password: option.Password, - ALPN: option.ALPN, - ServerName: option.Server, - SkipCertVerify: option.SkipCertVerify, + Password: option.Password, + Flow: option.Flow, + ALPN: option.ALPN, + ServerName: option.Server, + SkipCertVerify: option.SkipCertVerify, + ClientSessionCache: getClientSessionCache(), + ClientXSessionCache: getClientXSessionCache(), } if option.SNI != "" { diff --git a/adapter/outbound/util.go b/adapter/outbound/util.go index b376522f..ff6a8e65 100644 --- a/adapter/outbound/util.go +++ b/adapter/outbound/util.go @@ -2,8 +2,11 @@ package outbound import ( "bytes" + "crypto/tls" + xtls "github.com/xtls/go" "net" "strconv" + "sync" "time" "github.com/Dreamacro/clash/component/resolver" @@ -11,6 +14,12 @@ import ( "github.com/Dreamacro/clash/transport/socks5" ) +var ( + globalClientSessionCache tls.ClientSessionCache + globalClientXSessionCache xtls.ClientSessionCache + once sync.Once +) + func tcpKeepAlive(c net.Conn) { if tcp, ok := c.(*net.TCPConn); ok { tcp.SetKeepAlive(true) @@ -18,6 +27,20 @@ func tcpKeepAlive(c net.Conn) { } } +func getClientSessionCache() tls.ClientSessionCache { + once.Do(func() { + globalClientSessionCache = tls.NewLRUClientSessionCache(128) + }) + return globalClientSessionCache +} + +func getClientXSessionCache() xtls.ClientSessionCache { + once.Do(func() { + globalClientXSessionCache = xtls.NewLRUClientSessionCache(128) + }) + return globalClientXSessionCache +} + func serializesSocksAddr(metadata *C.Metadata) []byte { var buf [][]byte aType := uint8(metadata.AddrType) diff --git a/component/geodata/utils.go b/component/geodata/utils.go index 3a48dc86..1784fcee 100644 --- a/component/geodata/utils.go +++ b/component/geodata/utils.go @@ -2,10 +2,11 @@ package geodata import ( "github.com/Dreamacro/clash/component/geodata/router" + "strings" ) func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error) { - geoLoaderName := "standard" + geoLoaderName := "memconservative" geoLoader, err := GetGeoDataLoader(geoLoaderName) if err != nil { return nil, 0, err @@ -28,3 +29,29 @@ func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error) return matcher, len(domains), nil } + +func LoadGeoIPMatcher(country string) (*router.GeoIPMatcher, int, error) { + geoLoaderName := "memconservative" + geoLoader, err := GetGeoDataLoader(geoLoaderName) + if err != nil { + return nil, 0, err + } + + records, err := geoLoader.LoadGeoIP(strings.ReplaceAll(country, "!", "")) + if err != nil { + return nil, 0, err + } + + geoIP := &router.GeoIP{ + CountryCode: country, + Cidr: records, + ReverseMatch: strings.Contains(country, "!"), + } + + matcher, err := router.NewGeoIPMatcher(geoIP) + if err != nil { + return nil, 0, err + } + + return matcher, len(records), nil +} diff --git a/config/config.go b/config/config.go index bab6bbf1..99c2de9e 100644 --- a/config/config.go +++ b/config/config.go @@ -40,6 +40,7 @@ type General struct { LogLevel log.LogLevel `json:"log-level"` IPv6 bool `json:"ipv6"` Interface string `json:"-"` + Geodataload string `json:"geodataload"` } // Inbound @@ -169,6 +170,7 @@ type RawConfig struct { ExternalUI string `yaml:"external-ui"` Secret string `yaml:"secret"` Interface string `yaml:"interface-name"` + Geodataloader string `yaml:"geodata-loader"` ProxyProvider map[string]map[string]interface{} `yaml:"proxy-providers"` RuleProvider map[string]map[string]interface{} `yaml:"rule-providers"` @@ -199,6 +201,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { AllowLan: false, BindAddress: "*", Mode: T.Rule, + Geodataloader: "standard", UnifiedDelay: false, Authentication: []string{}, LogLevel: log.INFO, diff --git a/rule/common/geoip.go b/rule/common/geoip.go index 26205cc4..f0b7541d 100644 --- a/rule/common/geoip.go +++ b/rule/common/geoip.go @@ -55,33 +55,18 @@ func (g *GEOIP) GetCountry() string { return g.country } +func (g *GEOIP) GetIPMatcher() *router.GeoIPMatcher { + return g.geoIPMatcher +} + func NewGEOIP(country string, adapter string, noResolveIP bool, ruleExtra *C.RuleExtra) (*GEOIP, error) { - - geoLoaderName := "standard" - //geoLoaderName := "memconservative" - geoLoader, err := geodata.GetGeoDataLoader(geoLoaderName) + geoIPMatcher, recordsCount, err := geodata.LoadGeoIPMatcher(country) if err != nil { return nil, fmt.Errorf("[GeoIP] %s", err.Error()) } - records, err := geoLoader.LoadGeoIP(strings.ReplaceAll(country, "!", "")) - if err != nil { - return nil, fmt.Errorf("[GeoIP] %s", err.Error()) - } + log.Infoln("Start initial GeoIP rule %s => %s, records: %d", country, adapter, recordsCount) - geoIP := &router.GeoIP{ - CountryCode: country, - Cidr: records, - ReverseMatch: strings.Contains(country, "!"), - } - - geoIPMatcher, err := router.NewGeoIPMatcher(geoIP) - - if err != nil { - return nil, fmt.Errorf("[GeoIP] %s", err.Error()) - } - - log.Infoln("Start initial GeoIP rule %s => %s, records: %d", country, adapter, len(records)) geoip := &GEOIP{ country: country, adapter: adapter, diff --git a/rule/common/geosite.go b/rule/common/geosite.go index fcc04c17..95b6a2ff 100644 --- a/rule/common/geosite.go +++ b/rule/common/geosite.go @@ -8,6 +8,7 @@ import ( C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" + _ "github.com/Dreamacro/clash/component/geodata/memconservative" _ "github.com/Dreamacro/clash/component/geodata/standard" ) diff --git a/transport/trojan/trojan.go b/transport/trojan/trojan.go index ac9f17dd..e05ea2ee 100644 --- a/transport/trojan/trojan.go +++ b/transport/trojan/trojan.go @@ -7,6 +7,7 @@ import ( "encoding/binary" "encoding/hex" "errors" + xtls "github.com/xtls/go" "io" "net" "net/http" @@ -21,6 +22,9 @@ import ( const ( // max packet length maxLength = 8192 + + XRD = "xtls-rprx-direct" + XRO = "xtls-rprx-origin" ) var ( @@ -35,13 +39,18 @@ type Command = byte var ( CommandTCP byte = 1 CommandUDP byte = 3 + CommandXRD byte = 0xf0 + CommandXRO byte = 0xf1 ) type Option struct { - Password string - ALPN []string - ServerName string - SkipCertVerify bool + Password string + Flow string + ALPN []string + ServerName string + SkipCertVerify bool + ClientSessionCache tls.ClientSessionCache + ClientXSessionCache xtls.ClientSessionCache } type WebsocketOption struct { @@ -56,29 +65,55 @@ type Trojan struct { hexPassword []byte } +func (t *Trojan) GetFlow() string { + return t.option.Flow +} + func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) { alpn := defaultALPN if len(t.option.ALPN) != 0 { alpn = t.option.ALPN } + switch t.option.Flow { + case XRD, XRO: + xtlsConfig := &xtls.Config{ + NextProtos: alpn, + MinVersion: xtls.VersionTLS12, + InsecureSkipVerify: t.option.SkipCertVerify, + ServerName: t.option.ServerName, + ClientSessionCache: t.option.ClientXSessionCache, + } + xtlsConn := xtls.Client(conn, xtlsConfig) + if err := xtlsConn.Handshake(); err != nil { + return nil, err + } + ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout) + defer cancel() + if err := xtlsConn.HandshakeContext(ctx); err != nil { + return nil, err + } + return xtlsConn, nil + default: + tlsConfig := &tls.Config{ + NextProtos: alpn, + MinVersion: tls.VersionTLS12, + InsecureSkipVerify: t.option.SkipCertVerify, + ServerName: t.option.ServerName, + ClientSessionCache: t.option.ClientSessionCache, + } + 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() + if err := tlsConn.HandshakeContext(ctx); err != nil { + return nil, err + } - tlsConfig := &tls.Config{ - NextProtos: alpn, - MinVersion: tls.VersionTLS12, - InsecureSkipVerify: t.option.SkipCertVerify, - ServerName: t.option.ServerName, + return tlsConn, nil } - - tlsConn := tls.Client(conn, tlsConfig) - - // fix tls handshake not timeout - ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout) - defer cancel() - if err := tlsConn.HandshakeContext(ctx); err != nil { - return nil, err - } - - return tlsConn, nil } func (t *Trojan) StreamWebsocketConn(conn net.Conn, wsOptions *WebsocketOption) (net.Conn, error) {