support trojan xtls
change geodataloader mode as memconservative
This commit is contained in:
gVisor bot 2022-02-04 23:33:36 +08:00
parent cdc8baf44e
commit c57d92d7c1
8 changed files with 138 additions and 92 deletions

View file

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

View file

@ -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
} }
@ -157,9 +170,12 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
tOption := &trojan.Option{ tOption := &trojan.Option{
Password: option.Password, Password: option.Password,
Flow: option.Flow,
ALPN: option.ALPN, ALPN: option.ALPN,
ServerName: option.Server, ServerName: option.Server,
SkipCertVerify: option.SkipCertVerify, SkipCertVerify: option.SkipCertVerify,
ClientSessionCache: getClientSessionCache(),
ClientXSessionCache: getClientXSessionCache(),
} }
if option.SNI != "" { if option.SNI != "" {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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
Flow string
ALPN []string ALPN []string
ServerName string ServerName string
SkipCertVerify bool SkipCertVerify bool
ClientSessionCache tls.ClientSessionCache
ClientXSessionCache xtls.ClientSessionCache
} }
type WebsocketOption struct { type WebsocketOption struct {
@ -56,21 +65,46 @@ 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{ tlsConfig := &tls.Config{
NextProtos: alpn, NextProtos: alpn,
MinVersion: tls.VersionTLS12, MinVersion: tls.VersionTLS12,
InsecureSkipVerify: t.option.SkipCertVerify, InsecureSkipVerify: t.option.SkipCertVerify,
ServerName: t.option.ServerName, ServerName: t.option.ServerName,
ClientSessionCache: t.option.ClientSessionCache,
} }
tlsConn := tls.Client(conn, tlsConfig) tlsConn := tls.Client(conn, tlsConfig)
if err := tlsConn.Handshake(); err != nil {
return nil, err
}
// fix tls handshake not timeout // fix tls handshake not timeout
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout) ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
defer cancel() defer cancel()
@ -80,6 +114,7 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) {
return tlsConn, nil 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) {
alpn := defaultWebsocketALPN alpn := defaultWebsocketALPN