[Feat]
support trojan xtls change geodataloader mode as memconservative
This commit is contained in:
parent
cdc8baf44e
commit
c57d92d7c1
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"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
xtls "github.com/xtls/go"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -33,6 +34,7 @@ type TrojanOption struct {
|
||||||
Server string `proxy:"server"`
|
Server string `proxy:"server"`
|
||||||
Port int `proxy:"port"`
|
Port int `proxy:"port"`
|
||||||
Password string `proxy:"password"`
|
Password string `proxy:"password"`
|
||||||
|
Flow string `proxy:"flow,omitempty"`
|
||||||
ALPN []string `proxy:"alpn,omitempty"`
|
ALPN []string `proxy:"alpn,omitempty"`
|
||||||
SNI string `proxy:"sni,omitempty"`
|
SNI string `proxy:"sni,omitempty"`
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||||
}
|
}
|
||||||
|
var tc trojan.Command
|
||||||
err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata))
|
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
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,10 +169,13 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||||
|
|
||||||
tOption := &trojan.Option{
|
tOption := &trojan.Option{
|
||||||
Password: option.Password,
|
Password: option.Password,
|
||||||
ALPN: option.ALPN,
|
Flow: option.Flow,
|
||||||
ServerName: option.Server,
|
ALPN: option.ALPN,
|
||||||
SkipCertVerify: option.SkipCertVerify,
|
ServerName: option.Server,
|
||||||
|
SkipCertVerify: option.SkipCertVerify,
|
||||||
|
ClientSessionCache: getClientSessionCache(),
|
||||||
|
ClientXSessionCache: getClientXSessionCache(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if option.SNI != "" {
|
if option.SNI != "" {
|
||||||
|
|
|
@ -2,8 +2,11 @@ package outbound
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
xtls "github.com/xtls/go"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
|
@ -11,6 +14,12 @@ import (
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
globalClientSessionCache tls.ClientSessionCache
|
||||||
|
globalClientXSessionCache xtls.ClientSessionCache
|
||||||
|
once sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
func tcpKeepAlive(c net.Conn) {
|
func tcpKeepAlive(c net.Conn) {
|
||||||
if tcp, ok := c.(*net.TCPConn); ok {
|
if tcp, ok := c.(*net.TCPConn); ok {
|
||||||
tcp.SetKeepAlive(true)
|
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 {
|
func serializesSocksAddr(metadata *C.Metadata) []byte {
|
||||||
var buf [][]byte
|
var buf [][]byte
|
||||||
aType := uint8(metadata.AddrType)
|
aType := uint8(metadata.AddrType)
|
||||||
|
|
|
@ -2,10 +2,11 @@ package geodata
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/Dreamacro/clash/component/geodata/router"
|
"github.com/Dreamacro/clash/component/geodata/router"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error) {
|
func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error) {
|
||||||
geoLoaderName := "standard"
|
geoLoaderName := "memconservative"
|
||||||
geoLoader, err := GetGeoDataLoader(geoLoaderName)
|
geoLoader, err := GetGeoDataLoader(geoLoaderName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
|
@ -28,3 +29,29 @@ func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error)
|
||||||
|
|
||||||
return matcher, len(domains), nil
|
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"`
|
LogLevel log.LogLevel `json:"log-level"`
|
||||||
IPv6 bool `json:"ipv6"`
|
IPv6 bool `json:"ipv6"`
|
||||||
Interface string `json:"-"`
|
Interface string `json:"-"`
|
||||||
|
Geodataload string `json:"geodataload"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inbound
|
// Inbound
|
||||||
|
@ -169,6 +170,7 @@ type RawConfig struct {
|
||||||
ExternalUI string `yaml:"external-ui"`
|
ExternalUI string `yaml:"external-ui"`
|
||||||
Secret string `yaml:"secret"`
|
Secret string `yaml:"secret"`
|
||||||
Interface string `yaml:"interface-name"`
|
Interface string `yaml:"interface-name"`
|
||||||
|
Geodataloader string `yaml:"geodata-loader"`
|
||||||
|
|
||||||
ProxyProvider map[string]map[string]interface{} `yaml:"proxy-providers"`
|
ProxyProvider map[string]map[string]interface{} `yaml:"proxy-providers"`
|
||||||
RuleProvider map[string]map[string]interface{} `yaml:"rule-providers"`
|
RuleProvider map[string]map[string]interface{} `yaml:"rule-providers"`
|
||||||
|
@ -199,6 +201,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||||
AllowLan: false,
|
AllowLan: false,
|
||||||
BindAddress: "*",
|
BindAddress: "*",
|
||||||
Mode: T.Rule,
|
Mode: T.Rule,
|
||||||
|
Geodataloader: "standard",
|
||||||
UnifiedDelay: false,
|
UnifiedDelay: false,
|
||||||
Authentication: []string{},
|
Authentication: []string{},
|
||||||
LogLevel: log.INFO,
|
LogLevel: log.INFO,
|
||||||
|
|
|
@ -55,33 +55,18 @@ func (g *GEOIP) GetCountry() string {
|
||||||
return g.country
|
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) {
|
func NewGEOIP(country string, adapter string, noResolveIP bool, ruleExtra *C.RuleExtra) (*GEOIP, error) {
|
||||||
|
geoIPMatcher, recordsCount, err := geodata.LoadGeoIPMatcher(country)
|
||||||
geoLoaderName := "standard"
|
|
||||||
//geoLoaderName := "memconservative"
|
|
||||||
geoLoader, err := geodata.GetGeoDataLoader(geoLoaderName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("[GeoIP] %s", err.Error())
|
return nil, fmt.Errorf("[GeoIP] %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
records, err := geoLoader.LoadGeoIP(strings.ReplaceAll(country, "!", ""))
|
log.Infoln("Start initial GeoIP rule %s => %s, records: %d", country, adapter, recordsCount)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("[GeoIP] %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
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{
|
geoip := &GEOIP{
|
||||||
country: country,
|
country: country,
|
||||||
adapter: adapter,
|
adapter: adapter,
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
|
|
||||||
|
_ "github.com/Dreamacro/clash/component/geodata/memconservative"
|
||||||
_ "github.com/Dreamacro/clash/component/geodata/standard"
|
_ "github.com/Dreamacro/clash/component/geodata/standard"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
|
xtls "github.com/xtls/go"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -21,6 +22,9 @@ import (
|
||||||
const (
|
const (
|
||||||
// max packet length
|
// max packet length
|
||||||
maxLength = 8192
|
maxLength = 8192
|
||||||
|
|
||||||
|
XRD = "xtls-rprx-direct"
|
||||||
|
XRO = "xtls-rprx-origin"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -35,13 +39,18 @@ type Command = byte
|
||||||
var (
|
var (
|
||||||
CommandTCP byte = 1
|
CommandTCP byte = 1
|
||||||
CommandUDP byte = 3
|
CommandUDP byte = 3
|
||||||
|
CommandXRD byte = 0xf0
|
||||||
|
CommandXRO byte = 0xf1
|
||||||
)
|
)
|
||||||
|
|
||||||
type Option struct {
|
type Option struct {
|
||||||
Password string
|
Password string
|
||||||
ALPN []string
|
Flow string
|
||||||
ServerName string
|
ALPN []string
|
||||||
SkipCertVerify bool
|
ServerName string
|
||||||
|
SkipCertVerify bool
|
||||||
|
ClientSessionCache tls.ClientSessionCache
|
||||||
|
ClientXSessionCache xtls.ClientSessionCache
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebsocketOption struct {
|
type WebsocketOption struct {
|
||||||
|
@ -56,29 +65,55 @@ type Trojan struct {
|
||||||
hexPassword []byte
|
hexPassword []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Trojan) GetFlow() string {
|
||||||
|
return t.option.Flow
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) {
|
func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) {
|
||||||
alpn := defaultALPN
|
alpn := defaultALPN
|
||||||
if len(t.option.ALPN) != 0 {
|
if len(t.option.ALPN) != 0 {
|
||||||
alpn = t.option.ALPN
|
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{
|
return tlsConn, nil
|
||||||
NextProtos: alpn,
|
|
||||||
MinVersion: tls.VersionTLS12,
|
|
||||||
InsecureSkipVerify: t.option.SkipCertVerify,
|
|
||||||
ServerName: t.option.ServerName,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
func (t *Trojan) StreamWebsocketConn(conn net.Conn, wsOptions *WebsocketOption) (net.Conn, error) {
|
||||||
|
|
Loading…
Reference in a new issue