[Feat]
support trojan xtls change geodataloader mode as memconservative
This commit is contained in:
parent
35b19c3d7f
commit
2bf34c766e
8 changed files with 138 additions and 92 deletions
44
.github/workflows/dev.yml
vendored
44
.github/workflows/dev.yml
vendored
|
@ -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
|
||||
|
|
@ -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 != "" {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue