Merge remote-tracking branch 'origin/Alpha' into dev-restls
This commit is contained in:
commit
1bc3ecb027
35
adapter/outbound/reality.go
Normal file
35
adapter/outbound/reality.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package outbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/curve25519"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RealityOptions struct {
|
||||||
|
PublicKey string `proxy:"public-key"`
|
||||||
|
ShortID string `proxy:"short-id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o RealityOptions) Parse() (*tlsC.RealityConfig, error) {
|
||||||
|
if o.PublicKey != "" {
|
||||||
|
config := new(tlsC.RealityConfig)
|
||||||
|
|
||||||
|
n, err := base64.RawURLEncoding.Decode(config.PublicKey[:], []byte(o.PublicKey))
|
||||||
|
if err != nil || n != curve25519.ScalarSize {
|
||||||
|
return nil, errors.New("invalid REALITY public key")
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err = hex.Decode(config.ShortID[:], []byte(o.ShortID))
|
||||||
|
if err != nil || n > tlsC.RealityMaxShortIDLen {
|
||||||
|
return nil, errors.New("invalid REALITY short ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
@ -26,25 +26,28 @@ type Trojan struct {
|
|||||||
gunTLSConfig *tls.Config
|
gunTLSConfig *tls.Config
|
||||||
gunConfig *gun.Config
|
gunConfig *gun.Config
|
||||||
transport *gun.TransportWrap
|
transport *gun.TransportWrap
|
||||||
|
|
||||||
|
realityConfig *tlsC.RealityConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type TrojanOption struct {
|
type TrojanOption struct {
|
||||||
BasicOption
|
BasicOption
|
||||||
Name string `proxy:"name"`
|
Name string `proxy:"name"`
|
||||||
Server string `proxy:"server"`
|
Server string `proxy:"server"`
|
||||||
Port int `proxy:"port"`
|
Port int `proxy:"port"`
|
||||||
Password string `proxy:"password"`
|
Password string `proxy:"password"`
|
||||||
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"`
|
||||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||||
UDP bool `proxy:"udp,omitempty"`
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
Network string `proxy:"network,omitempty"`
|
Network string `proxy:"network,omitempty"`
|
||||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
||||||
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||||
Flow string `proxy:"flow,omitempty"`
|
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
||||||
FlowShow bool `proxy:"flow-show,omitempty"`
|
Flow string `proxy:"flow,omitempty"`
|
||||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
FlowShow bool `proxy:"flow-show,omitempty"`
|
||||||
|
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Trojan) plainStream(c net.Conn) (net.Conn, error) {
|
func (t *Trojan) plainStream(c net.Conn) (net.Conn, error) {
|
||||||
@ -83,7 +86,7 @@ func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if t.transport != nil {
|
if t.transport != nil {
|
||||||
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig)
|
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig, t.realityConfig)
|
||||||
} else {
|
} else {
|
||||||
c, err = t.plainStream(c)
|
c, err = t.plainStream(c)
|
||||||
}
|
}
|
||||||
@ -259,6 +262,13 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
|||||||
option: &option,
|
option: &option,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
t.realityConfig, err = option.RealityOpts.Parse()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tOption.Reality = t.realityConfig
|
||||||
|
|
||||||
if option.Network == "grpc" {
|
if option.Network == "grpc" {
|
||||||
dialFn := func(network, addr string) (net.Conn, error) {
|
dialFn := func(network, addr string) (net.Conn, error) {
|
||||||
c, err := dialer.DialContext(context.Background(), "tcp", t.addr, t.Base.DialOptions()...)
|
c, err := dialer.DialContext(context.Background(), "tcp", t.addr, t.Base.DialOptions()...)
|
||||||
@ -285,7 +295,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, tOption.ClientFingerprint)
|
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, tOption.ClientFingerprint, t.realityConfig)
|
||||||
|
|
||||||
t.gunTLSConfig = tlsConfig
|
t.gunTLSConfig = tlsConfig
|
||||||
t.gunConfig = &gun.Config{
|
t.gunConfig = &gun.Config{
|
||||||
|
@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
"github.com/Dreamacro/clash/transport/gun"
|
"github.com/Dreamacro/clash/transport/gun"
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
"github.com/Dreamacro/clash/transport/vless"
|
"github.com/Dreamacro/clash/transport/vless"
|
||||||
@ -41,6 +42,8 @@ type Vless struct {
|
|||||||
gunTLSConfig *tls.Config
|
gunTLSConfig *tls.Config
|
||||||
gunConfig *gun.Config
|
gunConfig *gun.Config
|
||||||
transport *gun.TransportWrap
|
transport *gun.TransportWrap
|
||||||
|
|
||||||
|
realityConfig *tlsC.RealityConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type VlessOption struct {
|
type VlessOption struct {
|
||||||
@ -57,6 +60,7 @@ type VlessOption struct {
|
|||||||
XUDP bool `proxy:"xudp,omitempty"`
|
XUDP bool `proxy:"xudp,omitempty"`
|
||||||
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
||||||
Network string `proxy:"network,omitempty"`
|
Network string `proxy:"network,omitempty"`
|
||||||
|
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
||||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||||
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||||
@ -78,7 +82,6 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
|
|
||||||
switch v.option.Network {
|
switch v.option.Network {
|
||||||
case "ws":
|
case "ws":
|
||||||
|
|
||||||
host, port, _ := net.SplitHostPort(v.addr)
|
host, port, _ := net.SplitHostPort(v.addr)
|
||||||
wsOpts := &vmess.WebsocketConfig{
|
wsOpts := &vmess.WebsocketConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
@ -154,7 +157,7 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
|
|
||||||
c, err = vmess.StreamH2Conn(c, h2Opts)
|
c, err = vmess.StreamH2Conn(c, h2Opts)
|
||||||
case "grpc":
|
case "grpc":
|
||||||
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig)
|
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig)
|
||||||
default:
|
default:
|
||||||
// default tcp network
|
// default tcp network
|
||||||
// handle TLS And XTLS
|
// handle TLS And XTLS
|
||||||
@ -190,6 +193,7 @@ func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error)
|
|||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
FingerPrint: v.option.Fingerprint,
|
FingerPrint: v.option.Fingerprint,
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
ClientFingerprint: v.option.ClientFingerprint,
|
||||||
|
Reality: v.realityConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
if isH2 {
|
if isH2 {
|
||||||
@ -479,7 +483,10 @@ func NewVless(option VlessOption) (*Vless, error) {
|
|||||||
if option.Network != "ws" && len(option.Flow) >= 16 {
|
if option.Network != "ws" && len(option.Flow) >= 16 {
|
||||||
option.Flow = option.Flow[:16]
|
option.Flow = option.Flow[:16]
|
||||||
switch option.Flow {
|
switch option.Flow {
|
||||||
case vless.XRO, vless.XRD, vless.XRS, vless.XRV:
|
case vless.XRV:
|
||||||
|
log.Warnln("To use %s, ensure your server is upgrade to Xray-core v1.8.0+", vless.XRV)
|
||||||
|
fallthrough
|
||||||
|
case vless.XRO, vless.XRD, vless.XRS:
|
||||||
addons = &vless.Addons{
|
addons = &vless.Addons{
|
||||||
Flow: option.Flow,
|
Flow: option.Flow,
|
||||||
}
|
}
|
||||||
@ -519,6 +526,11 @@ func NewVless(option VlessOption) (*Vless, error) {
|
|||||||
option: &option,
|
option: &option,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
v.realityConfig, err = v.option.RealityOpts.Parse()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
switch option.Network {
|
switch option.Network {
|
||||||
case "h2":
|
case "h2":
|
||||||
if len(option.HTTP2Opts.Host) == 0 {
|
if len(option.HTTP2Opts.Host) == 0 {
|
||||||
@ -553,8 +565,7 @@ func NewVless(option VlessOption) (*Vless, error) {
|
|||||||
v.gunTLSConfig = tlsConfig
|
v.gunTLSConfig = tlsConfig
|
||||||
v.gunConfig = gunConfig
|
v.gunConfig = gunConfig
|
||||||
|
|
||||||
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint)
|
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.realityConfig)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return v, nil
|
return v, nil
|
||||||
|
@ -35,32 +35,35 @@ type Vmess struct {
|
|||||||
gunTLSConfig *tls.Config
|
gunTLSConfig *tls.Config
|
||||||
gunConfig *gun.Config
|
gunConfig *gun.Config
|
||||||
transport *gun.TransportWrap
|
transport *gun.TransportWrap
|
||||||
|
|
||||||
|
realityConfig *tlsC.RealityConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type VmessOption struct {
|
type VmessOption struct {
|
||||||
BasicOption
|
BasicOption
|
||||||
Name string `proxy:"name"`
|
Name string `proxy:"name"`
|
||||||
Server string `proxy:"server"`
|
Server string `proxy:"server"`
|
||||||
Port int `proxy:"port"`
|
Port int `proxy:"port"`
|
||||||
UUID string `proxy:"uuid"`
|
UUID string `proxy:"uuid"`
|
||||||
AlterID int `proxy:"alterId"`
|
AlterID int `proxy:"alterId"`
|
||||||
Cipher string `proxy:"cipher"`
|
Cipher string `proxy:"cipher"`
|
||||||
UDP bool `proxy:"udp,omitempty"`
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
Network string `proxy:"network,omitempty"`
|
Network string `proxy:"network,omitempty"`
|
||||||
TLS bool `proxy:"tls,omitempty"`
|
TLS bool `proxy:"tls,omitempty"`
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||||
ServerName string `proxy:"servername,omitempty"`
|
ServerName string `proxy:"servername,omitempty"`
|
||||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
||||||
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||||
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||||
PacketAddr bool `proxy:"packet-addr,omitempty"`
|
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
||||||
XUDP bool `proxy:"xudp,omitempty"`
|
PacketAddr bool `proxy:"packet-addr,omitempty"`
|
||||||
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
XUDP bool `proxy:"xudp,omitempty"`
|
||||||
GlobalPadding bool `proxy:"global-padding,omitempty"`
|
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
||||||
AuthenticatedLength bool `proxy:"authenticated-length,omitempty"`
|
GlobalPadding bool `proxy:"global-padding,omitempty"`
|
||||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
AuthenticatedLength bool `proxy:"authenticated-length,omitempty"`
|
||||||
|
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HTTPOptions struct {
|
type HTTPOptions struct {
|
||||||
@ -95,7 +98,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
|
|
||||||
switch v.option.Network {
|
switch v.option.Network {
|
||||||
case "ws":
|
case "ws":
|
||||||
|
|
||||||
host, port, _ := net.SplitHostPort(v.addr)
|
host, port, _ := net.SplitHostPort(v.addr)
|
||||||
wsOpts := &clashVMess.WebsocketConfig{
|
wsOpts := &clashVMess.WebsocketConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
@ -144,12 +146,12 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
Host: host,
|
Host: host,
|
||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
ClientFingerprint: v.option.ClientFingerprint,
|
||||||
|
Reality: v.realityConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.option.ServerName != "" {
|
if v.option.ServerName != "" {
|
||||||
tlsOpts.Host = v.option.ServerName
|
tlsOpts.Host = v.option.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err = clashVMess.StreamTLSConn(c, tlsOpts)
|
c, err = clashVMess.StreamTLSConn(c, tlsOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -172,6 +174,7 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
NextProtos: []string{"h2"},
|
NextProtos: []string{"h2"},
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
ClientFingerprint: v.option.ClientFingerprint,
|
||||||
|
Reality: v.realityConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.option.ServerName != "" {
|
if v.option.ServerName != "" {
|
||||||
@ -190,7 +193,7 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
|
|
||||||
c, err = clashVMess.StreamH2Conn(c, h2Opts)
|
c, err = clashVMess.StreamH2Conn(c, h2Opts)
|
||||||
case "grpc":
|
case "grpc":
|
||||||
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig)
|
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig)
|
||||||
default:
|
default:
|
||||||
// handle TLS
|
// handle TLS
|
||||||
if v.option.TLS {
|
if v.option.TLS {
|
||||||
@ -199,6 +202,7 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
Host: host,
|
Host: host,
|
||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
ClientFingerprint: v.option.ClientFingerprint,
|
||||||
|
Reality: v.realityConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.option.ServerName != "" {
|
if v.option.ServerName != "" {
|
||||||
@ -451,9 +455,14 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||||||
v.gunTLSConfig = tlsConfig
|
v.gunTLSConfig = tlsConfig
|
||||||
v.gunConfig = gunConfig
|
v.gunConfig = gunConfig
|
||||||
|
|
||||||
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint)
|
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.realityConfig)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
v.realityConfig, err = v.option.RealityOpts.Parse()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ type wgNetDialer struct {
|
|||||||
var _ dialer.NetDialer = &wgNetDialer{}
|
var _ dialer.NetDialer = &wgNetDialer{}
|
||||||
|
|
||||||
func (d wgNetDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
func (d wgNetDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
return d.tunDevice.DialContext(ctx, network, M.ParseSocksaddr(address))
|
return d.tunDevice.DialContext(ctx, network, M.ParseSocksaddr(address).Unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
||||||
@ -221,11 +221,12 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts
|
|||||||
return nil, w.startErr
|
return nil, w.startErr
|
||||||
}
|
}
|
||||||
if !metadata.Resolved() {
|
if !metadata.Resolved() {
|
||||||
|
options = append(options, dialer.WithResolver(resolver.DefaultResolver))
|
||||||
options = append(options, dialer.WithNetDialer(wgNetDialer{tunDevice: w.tunDevice}))
|
options = append(options, dialer.WithNetDialer(wgNetDialer{tunDevice: w.tunDevice}))
|
||||||
conn, err = dialer.NewDialer(options...).DialContext(ctx, "tcp", metadata.RemoteAddress())
|
conn, err = dialer.NewDialer(options...).DialContext(ctx, "tcp", metadata.RemoteAddress())
|
||||||
} else {
|
} else {
|
||||||
port, _ := strconv.Atoi(metadata.DstPort)
|
port, _ := strconv.Atoi(metadata.DstPort)
|
||||||
conn, err = w.tunDevice.DialContext(ctx, "tcp", M.SocksaddrFrom(metadata.DstIP, uint16(port)))
|
conn, err = w.tunDevice.DialContext(ctx, "tcp", M.SocksaddrFrom(metadata.DstIP, uint16(port)).Unwrap())
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -257,7 +258,7 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
|
|||||||
metadata.DstIP = ip
|
metadata.DstIP = ip
|
||||||
}
|
}
|
||||||
port, _ := strconv.Atoi(metadata.DstPort)
|
port, _ := strconv.Atoi(metadata.DstPort)
|
||||||
pc, err = w.tunDevice.ListenPacket(ctx, M.SocksaddrFrom(metadata.DstIP, uint16(port)))
|
pc, err = w.tunDevice.ListenPacket(ctx, M.SocksaddrFrom(metadata.DstIP, uint16(port)).Unwrap())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -131,21 +131,23 @@ func strategyRoundRobin() strategyFn {
|
|||||||
idx := 0
|
idx := 0
|
||||||
idxMutex := sync.Mutex{}
|
idxMutex := sync.Mutex{}
|
||||||
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
|
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
|
||||||
id := idx // value could be wrong due to no lock, but don't care if we don't touch
|
idxMutex.Lock()
|
||||||
|
defer idxMutex.Unlock()
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
length := len(proxies)
|
||||||
|
|
||||||
if touch {
|
if touch {
|
||||||
idxMutex.Lock()
|
|
||||||
defer idxMutex.Unlock()
|
|
||||||
id = idx // get again by lock's protect, so it must be right
|
|
||||||
defer func() {
|
defer func() {
|
||||||
idx = id
|
idx = (idx + i) % length
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
length := len(proxies)
|
for ; i < length; i++ {
|
||||||
for i := 0; i < length; i++ {
|
id := (idx + i) % length
|
||||||
id = (id + 1) % length
|
|
||||||
proxy := proxies[id]
|
proxy := proxies[id]
|
||||||
if proxy.Alive() {
|
if proxy.Alive() {
|
||||||
|
i++
|
||||||
return proxy
|
return proxy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -216,7 +218,7 @@ func strategyStickySessions() strategyFn {
|
|||||||
// Unwrap implements C.ProxyAdapter
|
// Unwrap implements C.ProxyAdapter
|
||||||
func (lb *LoadBalance) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
|
func (lb *LoadBalance) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
|
||||||
proxies := lb.GetProxies(touch)
|
proxies := lb.GetProxies(touch)
|
||||||
return lb.strategyFn(proxies, metadata, true)
|
return lb.strategyFn(proxies, metadata, touch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements C.ProxyAdapter
|
// MarshalJSON implements C.ProxyAdapter
|
||||||
|
@ -83,7 +83,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
|||||||
trojan["port"] = urlTrojan.Port()
|
trojan["port"] = urlTrojan.Port()
|
||||||
trojan["password"] = urlTrojan.User.Username()
|
trojan["password"] = urlTrojan.User.Username()
|
||||||
trojan["udp"] = true
|
trojan["udp"] = true
|
||||||
trojan["skip-cert-verify"] = false
|
trojan["skip-cert-verify"], _ = strconv.ParseBool(query.Get("allowInsecure"))
|
||||||
|
|
||||||
sni := query.Get("sni")
|
sni := query.Get("sni")
|
||||||
if sni != "" {
|
if sni != "" {
|
||||||
|
8
common/utils/must.go
Normal file
8
common/utils/must.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
func MustOK[T any](result T, ok bool) T {
|
||||||
|
if ok {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
panic("operation failed")
|
||||||
|
}
|
34
common/utils/slice.go
Normal file
34
common/utils/slice.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Filter[T comparable](tSlice []T, filter func(t T) bool) []T {
|
||||||
|
result := make([]T, 0)
|
||||||
|
for _, t := range tSlice {
|
||||||
|
if filter(t) {
|
||||||
|
result = append(result, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToStringSlice(value any) ([]string, error) {
|
||||||
|
strArr := make([]string, 0)
|
||||||
|
switch reflect.TypeOf(value).Kind() {
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
origin := reflect.ValueOf(value)
|
||||||
|
for i := 0; i < origin.Len(); i++ {
|
||||||
|
item := fmt.Sprintf("%v", origin.Index(i))
|
||||||
|
strArr = append(strArr, item)
|
||||||
|
}
|
||||||
|
case reflect.String:
|
||||||
|
strArr = append(strArr, fmt.Sprintf("%v", value))
|
||||||
|
default:
|
||||||
|
return nil, errors.New("value format error, must be string or array")
|
||||||
|
}
|
||||||
|
return strArr, nil
|
||||||
|
}
|
@ -116,7 +116,8 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
|
|||||||
case nil:
|
case nil:
|
||||||
netDialer = &net.Dialer{}
|
netDialer = &net.Dialer{}
|
||||||
case *net.Dialer:
|
case *net.Dialer:
|
||||||
netDialer = &*netDialer.(*net.Dialer) // make a copy
|
_netDialer := *netDialer.(*net.Dialer)
|
||||||
|
netDialer = &_netDialer // make a copy
|
||||||
default:
|
default:
|
||||||
return netDialer.DialContext(ctx, network, address)
|
return netDialer.DialContext(ctx, network, address)
|
||||||
}
|
}
|
||||||
@ -170,7 +171,7 @@ func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string,
|
|||||||
select {
|
select {
|
||||||
case results <- result:
|
case results <- result:
|
||||||
case <-returned:
|
case <-returned:
|
||||||
if result.Conn != nil {
|
if result.Conn != nil && result.error == nil {
|
||||||
_ = result.Conn.Close()
|
_ = result.Conn.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -180,29 +181,33 @@ func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string,
|
|||||||
go racer(ipv4s, preferIPVersion != 6)
|
go racer(ipv4s, preferIPVersion != 6)
|
||||||
go racer(ipv6s, preferIPVersion != 4)
|
go racer(ipv6s, preferIPVersion != 4)
|
||||||
var fallback dialResult
|
var fallback dialResult
|
||||||
var err error
|
var errs []error
|
||||||
for {
|
for i := 0; i < 2; {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
|
||||||
if fallback.error == nil && fallback.Conn != nil {
|
|
||||||
return fallback.Conn, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("dual stack connect failed: %w", err)
|
|
||||||
case <-fallbackTicker.C:
|
case <-fallbackTicker.C:
|
||||||
if fallback.error == nil && fallback.Conn != nil {
|
if fallback.error == nil && fallback.Conn != nil {
|
||||||
return fallback.Conn, nil
|
return fallback.Conn, nil
|
||||||
}
|
}
|
||||||
case res := <-results:
|
case res := <-results:
|
||||||
|
i++
|
||||||
if res.error == nil {
|
if res.error == nil {
|
||||||
if res.isPrimary {
|
if res.isPrimary {
|
||||||
return res.Conn, nil
|
return res.Conn, nil
|
||||||
}
|
}
|
||||||
fallback = res
|
fallback = res
|
||||||
} else {
|
} else {
|
||||||
err = res.error
|
if res.isPrimary {
|
||||||
|
errs = append([]error{fmt.Errorf("connect failed: %w", res.error)}, errs...)
|
||||||
|
} else {
|
||||||
|
errs = append(errs, fmt.Errorf("connect failed: %w", res.error))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if fallback.error == nil && fallback.Conn != nil {
|
||||||
|
return fallback.Conn, nil
|
||||||
|
}
|
||||||
|
return nil, errorsJoin(errs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parallelDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
func parallelDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
||||||
@ -213,41 +218,35 @@ func parallelDialContext(ctx context.Context, network string, ips []netip.Addr,
|
|||||||
returned := make(chan struct{})
|
returned := make(chan struct{})
|
||||||
defer close(returned)
|
defer close(returned)
|
||||||
racer := func(ctx context.Context, ip netip.Addr) {
|
racer := func(ctx context.Context, ip netip.Addr) {
|
||||||
result := dialResult{isPrimary: true}
|
result := dialResult{isPrimary: true, ip: ip}
|
||||||
defer func() {
|
defer func() {
|
||||||
select {
|
select {
|
||||||
case results <- result:
|
case results <- result:
|
||||||
case <-returned:
|
case <-returned:
|
||||||
if result.Conn != nil {
|
if result.Conn != nil && result.error == nil {
|
||||||
_ = result.Conn.Close()
|
_ = result.Conn.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
result.ip = ip
|
|
||||||
result.Conn, result.error = dialContext(ctx, network, ip, port, opt)
|
result.Conn, result.error = dialContext(ctx, network, ip, port, opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
go racer(ctx, ip)
|
go racer(ctx, ip)
|
||||||
}
|
}
|
||||||
var err error
|
var errs []error
|
||||||
for {
|
for i := 0; i < len(ips); i++ {
|
||||||
select {
|
res := <-results
|
||||||
case <-ctx.Done():
|
if res.error == nil {
|
||||||
if err != nil {
|
return res.Conn, nil
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if ctx.Err() == context.DeadlineExceeded {
|
|
||||||
return nil, os.ErrDeadlineExceeded
|
|
||||||
}
|
|
||||||
return nil, ctx.Err()
|
|
||||||
case res := <-results:
|
|
||||||
if res.error == nil {
|
|
||||||
return res.Conn, nil
|
|
||||||
}
|
|
||||||
err = res.error
|
|
||||||
}
|
}
|
||||||
|
errs = append(errs, res.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return nil, errorsJoin(errs...)
|
||||||
|
}
|
||||||
|
return nil, os.ErrDeadlineExceeded
|
||||||
}
|
}
|
||||||
|
|
||||||
func serialDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
func serialDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
||||||
@ -302,13 +301,18 @@ func parseAddr(ctx context.Context, network, address string, preferResolver reso
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "-1", fmt.Errorf("dns resolve failed: %w", err)
|
return nil, "-1", fmt.Errorf("dns resolve failed: %w", err)
|
||||||
}
|
}
|
||||||
|
for i, ip := range ips {
|
||||||
|
if ip.Is4In6() {
|
||||||
|
ips[i] = ip.Unmap()
|
||||||
|
}
|
||||||
|
}
|
||||||
return ips, port, nil
|
return ips, port, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sortationAddr(ips []netip.Addr) (ipv4s, ipv6s []netip.Addr) {
|
func sortationAddr(ips []netip.Addr) (ipv4s, ipv6s []netip.Addr) {
|
||||||
for _, v := range ips {
|
for _, v := range ips {
|
||||||
if v.Is4() || v.Is4In6() {
|
if v.Is4() { // 4in6 parse was in parseAddr
|
||||||
ipv4s = append(ipv4s, v.Unmap())
|
ipv4s = append(ipv4s, v)
|
||||||
} else {
|
} else {
|
||||||
ipv6s = append(ipv6s, v)
|
ipv6s = append(ipv6s, v)
|
||||||
}
|
}
|
||||||
|
113
component/resolver/host.go
Normal file
113
component/resolver/host.go
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
package resolver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/netip"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/utils"
|
||||||
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
|
"github.com/zhangyunhao116/fastrand"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Hosts struct {
|
||||||
|
*trie.DomainTrie[HostValue]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHosts(hosts *trie.DomainTrie[HostValue]) Hosts {
|
||||||
|
return Hosts{
|
||||||
|
hosts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the search result and whether to match the parameter `isDomain`
|
||||||
|
func (h *Hosts) Search(domain string, isDomain bool) (*HostValue, bool) {
|
||||||
|
value := h.DomainTrie.Search(domain)
|
||||||
|
if value == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
hostValue := value.Data()
|
||||||
|
for {
|
||||||
|
if isDomain && hostValue.IsDomain {
|
||||||
|
return &hostValue, true
|
||||||
|
} else {
|
||||||
|
if node := h.DomainTrie.Search(hostValue.Domain); node != nil {
|
||||||
|
hostValue = node.Data()
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isDomain == hostValue.IsDomain {
|
||||||
|
return &hostValue, true
|
||||||
|
}
|
||||||
|
return &hostValue, false
|
||||||
|
}
|
||||||
|
|
||||||
|
type HostValue struct {
|
||||||
|
IsDomain bool
|
||||||
|
IPs []netip.Addr
|
||||||
|
Domain string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHostValue(value any) (HostValue, error) {
|
||||||
|
isDomain := true
|
||||||
|
ips := make([]netip.Addr, 0)
|
||||||
|
domain := ""
|
||||||
|
if valueArr, err := utils.ToStringSlice(value); err != nil {
|
||||||
|
return HostValue{}, err
|
||||||
|
} else {
|
||||||
|
if len(valueArr) > 1 {
|
||||||
|
isDomain = false
|
||||||
|
for _, str := range valueArr {
|
||||||
|
if ip, err := netip.ParseAddr(str); err == nil {
|
||||||
|
ips = append(ips, ip)
|
||||||
|
} else {
|
||||||
|
return HostValue{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if len(valueArr) == 1 {
|
||||||
|
host := valueArr[0]
|
||||||
|
if ip, err := netip.ParseAddr(host); err == nil {
|
||||||
|
ips = append(ips, ip)
|
||||||
|
isDomain = false
|
||||||
|
} else {
|
||||||
|
domain = host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isDomain {
|
||||||
|
return NewHostValueByDomain(domain)
|
||||||
|
} else {
|
||||||
|
return NewHostValueByIPs(ips)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHostValueByIPs(ips []netip.Addr) (HostValue, error) {
|
||||||
|
if len(ips) == 0 {
|
||||||
|
return HostValue{}, errors.New("ip list is empty")
|
||||||
|
}
|
||||||
|
return HostValue{
|
||||||
|
IsDomain: false,
|
||||||
|
IPs: ips,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHostValueByDomain(domain string) (HostValue, error) {
|
||||||
|
domain = strings.Trim(domain, ".")
|
||||||
|
item := strings.Split(domain, ".")
|
||||||
|
if len(item) < 2 {
|
||||||
|
return HostValue{}, errors.New("invaild domain")
|
||||||
|
}
|
||||||
|
return HostValue{
|
||||||
|
IsDomain: true,
|
||||||
|
Domain: domain,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hv HostValue) RandIP() (netip.Addr, error) {
|
||||||
|
if hv.IsDomain {
|
||||||
|
return netip.Addr{}, errors.New("value type is error")
|
||||||
|
}
|
||||||
|
return hv.IPs[fastrand.Intn(len(hv.IPs))], nil
|
||||||
|
}
|
@ -9,6 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/utils"
|
||||||
"github.com/Dreamacro/clash/component/trie"
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
@ -27,7 +28,7 @@ var (
|
|||||||
DisableIPv6 = true
|
DisableIPv6 = true
|
||||||
|
|
||||||
// DefaultHosts aim to resolve hosts
|
// DefaultHosts aim to resolve hosts
|
||||||
DefaultHosts = trie.New[netip.Addr]()
|
DefaultHosts = NewHosts(trie.New[HostValue]())
|
||||||
|
|
||||||
// DefaultDNSTimeout defined the default dns request timeout
|
// DefaultDNSTimeout defined the default dns request timeout
|
||||||
DefaultDNSTimeout = time.Second * 5
|
DefaultDNSTimeout = time.Second * 5
|
||||||
@ -51,9 +52,11 @@ type Resolver interface {
|
|||||||
|
|
||||||
// LookupIPv4WithResolver same as LookupIPv4, but with a resolver
|
// LookupIPv4WithResolver same as LookupIPv4, but with a resolver
|
||||||
func LookupIPv4WithResolver(ctx context.Context, host string, r Resolver) ([]netip.Addr, error) {
|
func LookupIPv4WithResolver(ctx context.Context, host string, r Resolver) ([]netip.Addr, error) {
|
||||||
if node := DefaultHosts.Search(host); node != nil {
|
if node, ok := DefaultHosts.Search(host, false); ok {
|
||||||
if ip := node.Data(); ip.Is4() {
|
if addrs := utils.Filter(node.IPs, func(ip netip.Addr) bool {
|
||||||
return []netip.Addr{node.Data()}, nil
|
return ip.Is4()
|
||||||
|
}); len(addrs) > 0 {
|
||||||
|
return addrs, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,9 +109,11 @@ func LookupIPv6WithResolver(ctx context.Context, host string, r Resolver) ([]net
|
|||||||
return nil, ErrIPv6Disabled
|
return nil, ErrIPv6Disabled
|
||||||
}
|
}
|
||||||
|
|
||||||
if node := DefaultHosts.Search(host); node != nil {
|
if node, ok := DefaultHosts.Search(host, false); ok {
|
||||||
if ip := node.Data(); ip.Is6() {
|
if addrs := utils.Filter(node.IPs, func(ip netip.Addr) bool {
|
||||||
return []netip.Addr{ip}, nil
|
return ip.Is6()
|
||||||
|
}); len(addrs) > 0 {
|
||||||
|
return addrs, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,8 +160,8 @@ func ResolveIPv6(ctx context.Context, host string) (netip.Addr, error) {
|
|||||||
|
|
||||||
// LookupIPWithResolver same as LookupIP, but with a resolver
|
// LookupIPWithResolver same as LookupIP, but with a resolver
|
||||||
func LookupIPWithResolver(ctx context.Context, host string, r Resolver) ([]netip.Addr, error) {
|
func LookupIPWithResolver(ctx context.Context, host string, r Resolver) ([]netip.Addr, error) {
|
||||||
if node := DefaultHosts.Search(host); node != nil {
|
if node, ok := DefaultHosts.Search(host, false); ok {
|
||||||
return []netip.Addr{node.Data()}, nil
|
return node.IPs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if r != nil {
|
if r != nil {
|
||||||
|
169
component/tls/reality.go
Normal file
169
component/tls/reality.go
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/utils"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
|
||||||
|
utls "github.com/sagernet/utls"
|
||||||
|
"github.com/zhangyunhao116/fastrand"
|
||||||
|
"golang.org/x/crypto/curve25519"
|
||||||
|
"golang.org/x/crypto/hkdf"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const RealityMaxShortIDLen = 8
|
||||||
|
|
||||||
|
type RealityConfig struct {
|
||||||
|
PublicKey [curve25519.ScalarSize]byte
|
||||||
|
ShortID [RealityMaxShortIDLen]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) {
|
||||||
|
if fingerprint, exists := GetFingerprint(ClientFingerprint); exists {
|
||||||
|
verifier := &realityVerifier{
|
||||||
|
serverName: tlsConfig.ServerName,
|
||||||
|
}
|
||||||
|
uConfig := &utls.Config{
|
||||||
|
ServerName: tlsConfig.ServerName,
|
||||||
|
NextProtos: tlsConfig.NextProtos,
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
SessionTicketsDisabled: true,
|
||||||
|
VerifyPeerCertificate: verifier.VerifyPeerCertificate,
|
||||||
|
}
|
||||||
|
clientID := utls.ClientHelloID{
|
||||||
|
Client: fingerprint.Client,
|
||||||
|
Version: fingerprint.Version,
|
||||||
|
Seed: fingerprint.Seed,
|
||||||
|
}
|
||||||
|
uConn := utls.UClient(conn, uConfig, clientID)
|
||||||
|
verifier.UConn = uConn
|
||||||
|
err := uConn.BuildHandshakeState()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hello := uConn.HandshakeState.Hello
|
||||||
|
hello.SessionId = make([]byte, 32)
|
||||||
|
copy(hello.Raw[39:], hello.SessionId)
|
||||||
|
|
||||||
|
var nowTime time.Time
|
||||||
|
if uConfig.Time != nil {
|
||||||
|
nowTime = uConfig.Time()
|
||||||
|
} else {
|
||||||
|
nowTime = time.Now()
|
||||||
|
}
|
||||||
|
binary.BigEndian.PutUint64(hello.SessionId, uint64(nowTime.Unix()))
|
||||||
|
|
||||||
|
hello.SessionId[0] = 1
|
||||||
|
hello.SessionId[1] = 7
|
||||||
|
hello.SessionId[2] = 5
|
||||||
|
copy(hello.SessionId[8:], realityConfig.ShortID[:])
|
||||||
|
|
||||||
|
//log.Debugln("REALITY hello.sessionId[:16]: %v", hello.SessionId[:16])
|
||||||
|
|
||||||
|
authKey := uConn.HandshakeState.State13.EcdheParams.SharedKey(realityConfig.PublicKey[:])
|
||||||
|
if authKey == nil {
|
||||||
|
return nil, errors.New("nil auth_key")
|
||||||
|
}
|
||||||
|
verifier.authKey = authKey
|
||||||
|
_, err = hkdf.New(sha256.New, authKey, hello.Random[:20], []byte("REALITY")).Read(authKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
aesBlock, _ := aes.NewCipher(authKey)
|
||||||
|
aesGcmCipher, _ := cipher.NewGCM(aesBlock)
|
||||||
|
aesGcmCipher.Seal(hello.SessionId[:0], hello.Random[20:], hello.SessionId[:16], hello.Raw)
|
||||||
|
copy(hello.Raw[39:], hello.SessionId)
|
||||||
|
//log.Debugln("REALITY hello.sessionId: %v", hello.SessionId)
|
||||||
|
//log.Debugln("REALITY uConn.AuthKey: %v", authKey)
|
||||||
|
|
||||||
|
err = uConn.HandshakeContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugln("REALITY Authentication: %v", verifier.verified)
|
||||||
|
|
||||||
|
if !verifier.verified {
|
||||||
|
go realityClientFallback(uConn, uConfig.ServerName, clientID)
|
||||||
|
return nil, errors.New("REALITY authentication failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return uConn, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("unknown uTLS fingerprint")
|
||||||
|
}
|
||||||
|
|
||||||
|
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
|
||||||
|
defer uConn.Close()
|
||||||
|
client := http.Client{
|
||||||
|
Transport: &http2.Transport{
|
||||||
|
DialTLSContext: func(ctx context.Context, network, addr string, config *tls.Config) (net.Conn, error) {
|
||||||
|
return uConn, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
request, _ := http.NewRequest("GET", "https://"+serverName, nil)
|
||||||
|
request.Header.Set("User-Agent", fingerprint.Client)
|
||||||
|
request.AddCookie(&http.Cookie{Name: "padding", Value: strings.Repeat("0", fastrand.Intn(32)+30)})
|
||||||
|
response, err := client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//_, _ = io.Copy(io.Discard, response.Body)
|
||||||
|
time.Sleep(time.Duration(5 + fastrand.Int63n(10)))
|
||||||
|
response.Body.Close()
|
||||||
|
client.CloseIdleConnections()
|
||||||
|
}
|
||||||
|
|
||||||
|
type realityVerifier struct {
|
||||||
|
*utls.UConn
|
||||||
|
serverName string
|
||||||
|
authKey []byte
|
||||||
|
verified bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var pOffset = utils.MustOK(reflect.TypeOf((*utls.UConn)(nil)).Elem().FieldByName("peerCertificates")).Offset
|
||||||
|
|
||||||
|
func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||||
|
//p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates")
|
||||||
|
certs := *(*[]*x509.Certificate)(unsafe.Pointer(uintptr(unsafe.Pointer(c.Conn)) + pOffset))
|
||||||
|
if pub, ok := certs[0].PublicKey.(ed25519.PublicKey); ok {
|
||||||
|
h := hmac.New(sha512.New, c.authKey)
|
||||||
|
h.Write(pub)
|
||||||
|
if bytes.Equal(h.Sum(nil), certs[0].Signature) {
|
||||||
|
c.verified = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opts := x509.VerifyOptions{
|
||||||
|
DNSName: c.serverName,
|
||||||
|
Intermediates: x509.NewCertPool(),
|
||||||
|
}
|
||||||
|
for _, cert := range certs[1:] {
|
||||||
|
opts.Intermediates.AddCert(cert)
|
||||||
|
}
|
||||||
|
if _, err := certs[0].Verify(opts); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -89,6 +89,7 @@ func copyConfig(c *tls.Config) *utls.Config {
|
|||||||
return &utls.Config{
|
return &utls.Config{
|
||||||
RootCAs: c.RootCAs,
|
RootCAs: c.RootCAs,
|
||||||
ServerName: c.ServerName,
|
ServerName: c.ServerName,
|
||||||
|
NextProtos: c.NextProtos,
|
||||||
InsecureSkipVerify: c.InsecureSkipVerify,
|
InsecureSkipVerify: c.InsecureSkipVerify,
|
||||||
VerifyPeerCertificate: c.VerifyPeerCertificate,
|
VerifyPeerCertificate: c.VerifyPeerCertificate,
|
||||||
}
|
}
|
||||||
|
129
config/config.go
129
config/config.go
@ -4,12 +4,11 @@ import (
|
|||||||
"container/list"
|
"container/list"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -26,6 +25,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/component/geodata"
|
"github.com/Dreamacro/clash/component/geodata"
|
||||||
"github.com/Dreamacro/clash/component/geodata/router"
|
"github.com/Dreamacro/clash/component/geodata/router"
|
||||||
P "github.com/Dreamacro/clash/component/process"
|
P "github.com/Dreamacro/clash/component/process"
|
||||||
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
SNIFF "github.com/Dreamacro/clash/component/sniffer"
|
SNIFF "github.com/Dreamacro/clash/component/sniffer"
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
"github.com/Dreamacro/clash/component/trie"
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
@ -92,6 +92,7 @@ type DNS struct {
|
|||||||
Enable bool `yaml:"enable"`
|
Enable bool `yaml:"enable"`
|
||||||
PreferH3 bool `yaml:"prefer-h3"`
|
PreferH3 bool `yaml:"prefer-h3"`
|
||||||
IPv6 bool `yaml:"ipv6"`
|
IPv6 bool `yaml:"ipv6"`
|
||||||
|
IPv6Timeout uint `yaml:"ipv6-timeout"`
|
||||||
NameServer []dns.NameServer `yaml:"nameserver"`
|
NameServer []dns.NameServer `yaml:"nameserver"`
|
||||||
Fallback []dns.NameServer `yaml:"fallback"`
|
Fallback []dns.NameServer `yaml:"fallback"`
|
||||||
FallbackFilter FallbackFilter `yaml:"fallback-filter"`
|
FallbackFilter FallbackFilter `yaml:"fallback-filter"`
|
||||||
@ -99,7 +100,7 @@ type DNS struct {
|
|||||||
EnhancedMode C.DNSMode `yaml:"enhanced-mode"`
|
EnhancedMode C.DNSMode `yaml:"enhanced-mode"`
|
||||||
DefaultNameserver []dns.NameServer `yaml:"default-nameserver"`
|
DefaultNameserver []dns.NameServer `yaml:"default-nameserver"`
|
||||||
FakeIPRange *fakeip.Pool
|
FakeIPRange *fakeip.Pool
|
||||||
Hosts *trie.DomainTrie[netip.Addr]
|
Hosts *trie.DomainTrie[resolver.HostValue]
|
||||||
NameServerPolicy map[string][]dns.NameServer
|
NameServerPolicy map[string][]dns.NameServer
|
||||||
ProxyServerNameserver []dns.NameServer
|
ProxyServerNameserver []dns.NameServer
|
||||||
}
|
}
|
||||||
@ -153,7 +154,7 @@ type Config struct {
|
|||||||
IPTables *IPTables
|
IPTables *IPTables
|
||||||
DNS *DNS
|
DNS *DNS
|
||||||
Experimental *Experimental
|
Experimental *Experimental
|
||||||
Hosts *trie.DomainTrie[netip.Addr]
|
Hosts *trie.DomainTrie[resolver.HostValue]
|
||||||
Profile *Profile
|
Profile *Profile
|
||||||
Rules []C.Rule
|
Rules []C.Rule
|
||||||
SubRules map[string][]C.Rule
|
SubRules map[string][]C.Rule
|
||||||
@ -171,6 +172,7 @@ type RawDNS struct {
|
|||||||
Enable bool `yaml:"enable"`
|
Enable bool `yaml:"enable"`
|
||||||
PreferH3 bool `yaml:"prefer-h3"`
|
PreferH3 bool `yaml:"prefer-h3"`
|
||||||
IPv6 bool `yaml:"ipv6"`
|
IPv6 bool `yaml:"ipv6"`
|
||||||
|
IPv6Timeout uint `yaml:"ipv6-timeout"`
|
||||||
UseHosts bool `yaml:"use-hosts"`
|
UseHosts bool `yaml:"use-hosts"`
|
||||||
NameServer []string `yaml:"nameserver"`
|
NameServer []string `yaml:"nameserver"`
|
||||||
Fallback []string `yaml:"fallback"`
|
Fallback []string `yaml:"fallback"`
|
||||||
@ -263,7 +265,7 @@ type RawConfig struct {
|
|||||||
Sniffer RawSniffer `yaml:"sniffer"`
|
Sniffer RawSniffer `yaml:"sniffer"`
|
||||||
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
|
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
|
||||||
RuleProvider map[string]map[string]any `yaml:"rule-providers"`
|
RuleProvider map[string]map[string]any `yaml:"rule-providers"`
|
||||||
Hosts map[string]string `yaml:"hosts"`
|
Hosts map[string]any `yaml:"hosts"`
|
||||||
DNS RawDNS `yaml:"dns"`
|
DNS RawDNS `yaml:"dns"`
|
||||||
Tun RawTun `yaml:"tun"`
|
Tun RawTun `yaml:"tun"`
|
||||||
TuicServer RawTuicServer `yaml:"tuic-server"`
|
TuicServer RawTuicServer `yaml:"tuic-server"`
|
||||||
@ -337,7 +339,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
|||||||
UnifiedDelay: false,
|
UnifiedDelay: false,
|
||||||
Authentication: []string{},
|
Authentication: []string{},
|
||||||
LogLevel: log.INFO,
|
LogLevel: log.INFO,
|
||||||
Hosts: map[string]string{},
|
Hosts: map[string]any{},
|
||||||
Rule: []string{},
|
Rule: []string{},
|
||||||
Proxy: []map[string]any{},
|
Proxy: []map[string]any{},
|
||||||
ProxyGroup: []map[string]any{},
|
ProxyGroup: []map[string]any{},
|
||||||
@ -377,6 +379,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
|||||||
Enable: false,
|
Enable: false,
|
||||||
IPv6: false,
|
IPv6: false,
|
||||||
UseHosts: true,
|
UseHosts: true,
|
||||||
|
IPv6Timeout: 100,
|
||||||
EnhancedMode: C.DNSMapping,
|
EnhancedMode: C.DNSMapping,
|
||||||
FakeIPRange: "198.18.0.1/16",
|
FakeIPRange: "198.18.0.1/16",
|
||||||
FallbackFilter: RawFallbackFilter{
|
FallbackFilter: RawFallbackFilter{
|
||||||
@ -415,9 +418,9 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
|||||||
StoreSelected: true,
|
StoreSelected: true,
|
||||||
},
|
},
|
||||||
GeoXUrl: RawGeoXUrl{
|
GeoXUrl: RawGeoXUrl{
|
||||||
GeoIp: "https://ghproxy.com/https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geoip.dat",
|
Mmdb: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/country.mmdb",
|
||||||
Mmdb: "https://ghproxy.com/https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb",
|
GeoIp: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat",
|
||||||
GeoSite: "https://ghproxy.com/https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geosite.dat",
|
GeoSite: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -443,6 +446,11 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
|||||||
}
|
}
|
||||||
config.General = general
|
config.General = general
|
||||||
|
|
||||||
|
if len(config.General.GlobalClientFingerprint) != 0 {
|
||||||
|
log.Debugln("GlobalClientFingerprint:%s", config.General.GlobalClientFingerprint)
|
||||||
|
tlsC.SetGlobalUtlsClient(config.General.GlobalClientFingerprint)
|
||||||
|
}
|
||||||
|
|
||||||
dialer.DefaultInterface.Store(config.General.Interface)
|
dialer.DefaultInterface.Store(config.General.Interface)
|
||||||
proxies, providers, err := parseProxies(rawCfg)
|
proxies, providers, err := parseProxies(rawCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -518,11 +526,6 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
|||||||
elapsedTime := time.Since(startTime) / time.Millisecond // duration in ms
|
elapsedTime := time.Since(startTime) / time.Millisecond // duration in ms
|
||||||
log.Infoln("Initial configuration complete, total time: %dms", elapsedTime) //Segment finished in xxm
|
log.Infoln("Initial configuration complete, total time: %dms", elapsedTime) //Segment finished in xxm
|
||||||
|
|
||||||
if len(config.General.GlobalClientFingerprint) != 0 {
|
|
||||||
log.Debugln("GlobalClientFingerprint:%s", config.General.GlobalClientFingerprint)
|
|
||||||
tlsC.SetGlobalUtlsClient(config.General.GlobalClientFingerprint)
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -824,21 +827,47 @@ func parseRules(rulesConfig []string, proxies map[string]C.Proxy, subRules map[s
|
|||||||
return rules, nil
|
return rules, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseHosts(cfg *RawConfig) (*trie.DomainTrie[netip.Addr], error) {
|
func parseHosts(cfg *RawConfig) (*trie.DomainTrie[resolver.HostValue], error) {
|
||||||
tree := trie.New[netip.Addr]()
|
tree := trie.New[resolver.HostValue]()
|
||||||
|
|
||||||
// add default hosts
|
// add default hosts
|
||||||
if err := tree.Insert("localhost", netip.AddrFrom4([4]byte{127, 0, 0, 1})); err != nil {
|
hostValue, _ := resolver.NewHostValueByIPs(
|
||||||
|
[]netip.Addr{netip.AddrFrom4([4]byte{127, 0, 0, 1})})
|
||||||
|
if err := tree.Insert("localhost", hostValue); err != nil {
|
||||||
log.Errorln("insert localhost to host error: %s", err.Error())
|
log.Errorln("insert localhost to host error: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cfg.Hosts) != 0 {
|
if len(cfg.Hosts) != 0 {
|
||||||
for domain, ipStr := range cfg.Hosts {
|
for domain, anyValue := range cfg.Hosts {
|
||||||
ip, err := netip.ParseAddr(ipStr)
|
if str, ok := anyValue.(string); ok && str == "clash" {
|
||||||
if err != nil {
|
if addrs, err := net.InterfaceAddrs(); err != nil {
|
||||||
return nil, fmt.Errorf("%s is not a valid IP", ipStr)
|
log.Errorln("insert clash to host error: %s", err)
|
||||||
|
} else {
|
||||||
|
ips := make([]netip.Addr, 0)
|
||||||
|
for _, addr := range addrs {
|
||||||
|
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
||||||
|
if ip, err := netip.ParseAddr(ipnet.IP.String()); err == nil {
|
||||||
|
ips = append(ips, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
anyValue = ips
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ = tree.Insert(domain, ip)
|
value, err := resolver.NewHostValue(anyValue)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s is not a valid value", anyValue)
|
||||||
|
}
|
||||||
|
if value.IsDomain {
|
||||||
|
node := tree.Search(value.Domain)
|
||||||
|
for node != nil && node.Data().IsDomain {
|
||||||
|
if node.Data().Domain == domain {
|
||||||
|
return nil, fmt.Errorf("%s, there is a cycle in domain name mapping", domain)
|
||||||
|
}
|
||||||
|
node = tree.Search(node.Data().Domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = tree.Insert(domain, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tree.Optimize()
|
tree.Optimize()
|
||||||
@ -956,26 +985,39 @@ func parsePureDNSServer(server string) string {
|
|||||||
}
|
}
|
||||||
func parseNameServerPolicy(nsPolicy map[string]any, preferH3 bool) (map[string][]dns.NameServer, error) {
|
func parseNameServerPolicy(nsPolicy map[string]any, preferH3 bool) (map[string][]dns.NameServer, error) {
|
||||||
policy := map[string][]dns.NameServer{}
|
policy := map[string][]dns.NameServer{}
|
||||||
|
updatedPolicy := make(map[string]interface{})
|
||||||
|
re := regexp.MustCompile(`[a-zA-Z0-9\-]+\.[a-zA-Z]{2,}(\.[a-zA-Z]{2,})?`)
|
||||||
|
|
||||||
for domain, server := range nsPolicy {
|
for k, v := range nsPolicy {
|
||||||
var (
|
if strings.Contains(k, "geosite:") {
|
||||||
nameservers []dns.NameServer
|
subkeys := strings.Split(k, ":")
|
||||||
err error
|
subkeys = subkeys[1:]
|
||||||
)
|
subkeys = strings.Split(subkeys[0], ",")
|
||||||
|
//log.Infoln("subkeys:%+v", subkeys)
|
||||||
switch reflect.TypeOf(server).Kind() {
|
for _, subkey := range subkeys {
|
||||||
case reflect.Slice, reflect.Array:
|
newKey := "geosite:" + subkey
|
||||||
origin := reflect.ValueOf(server)
|
//log.Infoln("newKey:%+v", newKey)
|
||||||
servers := make([]string, 0)
|
updatedPolicy[newKey] = v
|
||||||
for i := 0; i < origin.Len(); i++ {
|
|
||||||
servers = append(servers, fmt.Sprintf("%v", origin.Index(i)))
|
|
||||||
}
|
}
|
||||||
nameservers, err = parseNameServer(servers, preferH3)
|
} else if re.MatchString(k) {
|
||||||
case reflect.String:
|
subkeys := strings.Split(k, ",")
|
||||||
nameservers, err = parseNameServer([]string{fmt.Sprintf("%v", server)}, preferH3)
|
//log.Infoln("subkeys:%+v", subkeys)
|
||||||
default:
|
for _, subkey := range subkeys {
|
||||||
return nil, errors.New("server format error, must be string or array")
|
updatedPolicy[subkey] = v
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updatedPolicy[k] = v
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
//log.Infoln("updatedPolicy:%+v", updatedPolicy)
|
||||||
|
|
||||||
|
for domain, server := range updatedPolicy {
|
||||||
|
|
||||||
|
servers, err := utils.ToStringSlice(server)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nameservers, err := parseNameServer(servers, preferH3)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1038,7 +1080,7 @@ func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainM
|
|||||||
return sites, nil
|
return sites, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[netip.Addr], rules []C.Rule) (*DNS, error) {
|
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rules []C.Rule) (*DNS, error) {
|
||||||
cfg := rawCfg.DNS
|
cfg := rawCfg.DNS
|
||||||
if cfg.Enable && len(cfg.NameServer) == 0 {
|
if cfg.Enable && len(cfg.NameServer) == 0 {
|
||||||
return nil, fmt.Errorf("if DNS configuration is turned on, NameServer cannot be empty")
|
return nil, fmt.Errorf("if DNS configuration is turned on, NameServer cannot be empty")
|
||||||
@ -1048,6 +1090,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[netip.Addr], rules []C.R
|
|||||||
Enable: cfg.Enable,
|
Enable: cfg.Enable,
|
||||||
Listen: cfg.Listen,
|
Listen: cfg.Listen,
|
||||||
PreferH3: cfg.PreferH3,
|
PreferH3: cfg.PreferH3,
|
||||||
|
IPv6Timeout: cfg.IPv6Timeout,
|
||||||
IPv6: cfg.IPv6,
|
IPv6: cfg.IPv6,
|
||||||
EnhancedMode: cfg.EnhancedMode,
|
EnhancedMode: cfg.EnhancedMode,
|
||||||
FallbackFilter: FallbackFilter{
|
FallbackFilter: FallbackFilter{
|
||||||
@ -1255,8 +1298,10 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Deprecated: Use Sniff instead
|
if sniffer.Enable {
|
||||||
log.Warnln("Deprecated: Use Sniff instead")
|
// Deprecated: Use Sniff instead
|
||||||
|
log.Warnln("Deprecated: Use Sniff instead")
|
||||||
|
}
|
||||||
globalPorts, err := parsePortRange(snifferRaw.Ports)
|
globalPorts, err := parsePortRange(snifferRaw.Ports)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/common/cache"
|
"github.com/Dreamacro/clash/common/cache"
|
||||||
"github.com/Dreamacro/clash/common/nnip"
|
"github.com/Dreamacro/clash/common/nnip"
|
||||||
"github.com/Dreamacro/clash/component/fakeip"
|
"github.com/Dreamacro/clash/component/fakeip"
|
||||||
"github.com/Dreamacro/clash/component/trie"
|
R "github.com/Dreamacro/clash/component/resolver"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/context"
|
"github.com/Dreamacro/clash/context"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
@ -21,7 +21,7 @@ type (
|
|||||||
middleware func(next handler) handler
|
middleware func(next handler) handler
|
||||||
)
|
)
|
||||||
|
|
||||||
func withHosts(hosts *trie.DomainTrie[netip.Addr], mapping *cache.LruCache[netip.Addr, string]) middleware {
|
func withHosts(hosts R.Hosts, mapping *cache.LruCache[netip.Addr, string]) middleware {
|
||||||
return func(next handler) handler {
|
return func(next handler) handler {
|
||||||
return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) {
|
return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) {
|
||||||
q := r.Question[0]
|
q := r.Question[0]
|
||||||
@ -31,40 +31,68 @@ func withHosts(hosts *trie.DomainTrie[netip.Addr], mapping *cache.LruCache[netip
|
|||||||
}
|
}
|
||||||
|
|
||||||
host := strings.TrimRight(q.Name, ".")
|
host := strings.TrimRight(q.Name, ".")
|
||||||
|
handleCName := func(resp *D.Msg, domain string) {
|
||||||
record := hosts.Search(host)
|
rr := &D.CNAME{}
|
||||||
if record == nil {
|
rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeCNAME, Class: D.ClassINET, Ttl: 10}
|
||||||
|
rr.Target = domain + "."
|
||||||
|
resp.Answer = append([]D.RR{rr}, resp.Answer...)
|
||||||
|
}
|
||||||
|
record, ok := hosts.Search(host, q.Qtype != D.TypeA && q.Qtype != D.TypeAAAA)
|
||||||
|
if !ok {
|
||||||
|
if record != nil && record.IsDomain {
|
||||||
|
// replace request domain
|
||||||
|
newR := r.Copy()
|
||||||
|
newR.Question[0].Name = record.Domain + "."
|
||||||
|
resp, err := next(ctx, newR)
|
||||||
|
if err == nil {
|
||||||
|
resp.Id = r.Id
|
||||||
|
resp.Question = r.Question
|
||||||
|
handleCName(resp, record.Domain)
|
||||||
|
}
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
return next(ctx, r)
|
return next(ctx, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
ip := record.Data()
|
|
||||||
msg := r.Copy()
|
msg := r.Copy()
|
||||||
|
handleIPs := func() {
|
||||||
if ip.Is4() && q.Qtype == D.TypeA {
|
for _, ipAddr := range record.IPs {
|
||||||
rr := &D.A{}
|
if ipAddr.Is4() && q.Qtype == D.TypeA {
|
||||||
rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeA, Class: D.ClassINET, Ttl: 10}
|
rr := &D.A{}
|
||||||
rr.A = ip.AsSlice()
|
rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeA, Class: D.ClassINET, Ttl: 10}
|
||||||
|
rr.A = ipAddr.AsSlice()
|
||||||
msg.Answer = []D.RR{rr}
|
msg.Answer = append(msg.Answer, rr)
|
||||||
} else if q.Qtype == D.TypeAAAA {
|
if mapping != nil {
|
||||||
rr := &D.AAAA{}
|
mapping.SetWithExpire(ipAddr, host, time.Now().Add(time.Second*10))
|
||||||
rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeAAAA, Class: D.ClassINET, Ttl: 10}
|
}
|
||||||
ip := ip.As16()
|
} else if q.Qtype == D.TypeAAAA {
|
||||||
rr.AAAA = ip[:]
|
rr := &D.AAAA{}
|
||||||
msg.Answer = []D.RR{rr}
|
rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeAAAA, Class: D.ClassINET, Ttl: 10}
|
||||||
} else {
|
ip := ipAddr.As16()
|
||||||
return next(ctx, r)
|
rr.AAAA = ip[:]
|
||||||
|
msg.Answer = append(msg.Answer, rr)
|
||||||
|
if mapping != nil {
|
||||||
|
mapping.SetWithExpire(ipAddr, host, time.Now().Add(time.Second*10))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if mapping != nil {
|
switch q.Qtype {
|
||||||
mapping.SetWithExpire(ip, host, time.Now().Add(time.Second*10))
|
case D.TypeA:
|
||||||
|
handleIPs()
|
||||||
|
case D.TypeAAAA:
|
||||||
|
handleIPs()
|
||||||
|
case D.TypeCNAME:
|
||||||
|
handleCName(r, record.Domain)
|
||||||
|
default:
|
||||||
|
return next(ctx, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.SetType(context.DNSTypeHost)
|
ctx.SetType(context.DNSTypeHost)
|
||||||
msg.SetRcode(r, D.RcodeSuccess)
|
msg.SetRcode(r, D.RcodeSuccess)
|
||||||
msg.Authoritative = true
|
msg.Authoritative = true
|
||||||
msg.RecursionAvailable = true
|
msg.RecursionAvailable = true
|
||||||
|
|
||||||
return msg, nil
|
return msg, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,6 +177,7 @@ func withFakeIP(fakePool *fakeip.Pool) middleware {
|
|||||||
func withResolver(resolver *Resolver) handler {
|
func withResolver(resolver *Resolver) handler {
|
||||||
return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) {
|
return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) {
|
||||||
ctx.SetType(context.DNSTypeRaw)
|
ctx.SetType(context.DNSTypeRaw)
|
||||||
|
|
||||||
q := r.Question[0]
|
q := r.Question[0]
|
||||||
|
|
||||||
// return a empty AAAA msg when ipv6 disabled
|
// return a empty AAAA msg when ipv6 disabled
|
||||||
@ -183,7 +212,7 @@ func NewHandler(resolver *Resolver, mapper *ResolverEnhancer) handler {
|
|||||||
middlewares := []middleware{}
|
middlewares := []middleware{}
|
||||||
|
|
||||||
if resolver.hosts != nil {
|
if resolver.hosts != nil {
|
||||||
middlewares = append(middlewares, withHosts(resolver.hosts, mapper.mapping))
|
middlewares = append(middlewares, withHosts(R.NewHosts(resolver.hosts), mapper.mapping))
|
||||||
}
|
}
|
||||||
|
|
||||||
if mapper.mode == C.DNSFakeIP {
|
if mapper.mode == C.DNSFakeIP {
|
||||||
|
@ -42,7 +42,8 @@ type geositePolicyRecord struct {
|
|||||||
|
|
||||||
type Resolver struct {
|
type Resolver struct {
|
||||||
ipv6 bool
|
ipv6 bool
|
||||||
hosts *trie.DomainTrie[netip.Addr]
|
ipv6Timeout time.Duration
|
||||||
|
hosts *trie.DomainTrie[resolver.HostValue]
|
||||||
main []dnsClient
|
main []dnsClient
|
||||||
fallback []dnsClient
|
fallback []dnsClient
|
||||||
fallbackDomainFilters []fallbackDomainFilter
|
fallbackDomainFilters []fallbackDomainFilter
|
||||||
@ -91,14 +92,20 @@ func (r *Resolver) LookupIP(ctx context.Context, host string) (ips []netip.Addr,
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
ips, err = r.lookupIP(ctx, host, D.TypeA)
|
ips, err = r.lookupIP(ctx, host, D.TypeA)
|
||||||
|
var waitIPv6 *time.Timer
|
||||||
|
if r != nil {
|
||||||
|
waitIPv6 = time.NewTimer(r.ipv6Timeout)
|
||||||
|
} else {
|
||||||
|
waitIPv6 = time.NewTimer(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
defer waitIPv6.Stop()
|
||||||
select {
|
select {
|
||||||
case ipv6s, open := <-ch:
|
case ipv6s, open := <-ch:
|
||||||
if !open && err != nil {
|
if !open && err != nil {
|
||||||
return nil, resolver.ErrIPNotFound
|
return nil, resolver.ErrIPNotFound
|
||||||
}
|
}
|
||||||
ips = append(ips, ipv6s...)
|
ips = append(ips, ipv6s...)
|
||||||
case <-time.After(30 * time.Millisecond):
|
case <-waitIPv6.C:
|
||||||
// wait ipv6 result
|
// wait ipv6 result
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -419,24 +426,27 @@ type Config struct {
|
|||||||
Default []NameServer
|
Default []NameServer
|
||||||
ProxyServer []NameServer
|
ProxyServer []NameServer
|
||||||
IPv6 bool
|
IPv6 bool
|
||||||
|
IPv6Timeout uint
|
||||||
EnhancedMode C.DNSMode
|
EnhancedMode C.DNSMode
|
||||||
FallbackFilter FallbackFilter
|
FallbackFilter FallbackFilter
|
||||||
Pool *fakeip.Pool
|
Pool *fakeip.Pool
|
||||||
Hosts *trie.DomainTrie[netip.Addr]
|
Hosts *trie.DomainTrie[resolver.HostValue]
|
||||||
Policy map[string][]NameServer
|
Policy map[string][]NameServer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResolver(config Config) *Resolver {
|
func NewResolver(config Config) *Resolver {
|
||||||
defaultResolver := &Resolver{
|
defaultResolver := &Resolver{
|
||||||
main: transform(config.Default, nil),
|
main: transform(config.Default, nil),
|
||||||
lruCache: cache.New(cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true)),
|
lruCache: cache.New(cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true)),
|
||||||
|
ipv6Timeout: time.Duration(config.IPv6Timeout) * time.Millisecond,
|
||||||
}
|
}
|
||||||
|
|
||||||
r := &Resolver{
|
r := &Resolver{
|
||||||
ipv6: config.IPv6,
|
ipv6: config.IPv6,
|
||||||
main: transform(config.Main, defaultResolver),
|
main: transform(config.Main, defaultResolver),
|
||||||
lruCache: cache.New(cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true)),
|
lruCache: cache.New(cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true)),
|
||||||
hosts: config.Hosts,
|
hosts: config.Hosts,
|
||||||
|
ipv6Timeout: time.Duration(config.IPv6Timeout) * time.Millisecond,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(config.Fallback) != 0 {
|
if len(config.Fallback) != 0 {
|
||||||
@ -502,11 +512,12 @@ func NewResolver(config Config) *Resolver {
|
|||||||
|
|
||||||
func NewProxyServerHostResolver(old *Resolver) *Resolver {
|
func NewProxyServerHostResolver(old *Resolver) *Resolver {
|
||||||
r := &Resolver{
|
r := &Resolver{
|
||||||
ipv6: old.ipv6,
|
ipv6: old.ipv6,
|
||||||
main: old.proxyServer,
|
main: old.proxyServer,
|
||||||
lruCache: old.lruCache,
|
lruCache: old.lruCache,
|
||||||
hosts: old.hosts,
|
hosts: old.hosts,
|
||||||
policy: old.policy,
|
policy: old.policy,
|
||||||
|
ipv6Timeout: old.ipv6Timeout,
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ func setMsgTTL(msg *D.Msg, ttl uint32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isIPRequest(q D.Question) bool {
|
func isIPRequest(q D.Question) bool {
|
||||||
return q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA)
|
return q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA || q.Qtype == D.TypeCNAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
||||||
|
@ -58,9 +58,11 @@ hosts:
|
|||||||
# '*.clash.dev': 127.0.0.1
|
# '*.clash.dev': 127.0.0.1
|
||||||
# '.dev': 127.0.0.1
|
# '.dev': 127.0.0.1
|
||||||
# 'alpha.clash.dev': '::1'
|
# 'alpha.clash.dev': '::1'
|
||||||
|
# test.com: [1.1.1.1, 2.2.2.2]
|
||||||
|
# clash.lan: clash # clash 为特别字段,将加入本地所有网卡的地址
|
||||||
|
# baidu.com: google.com # 只允许配置一个别名
|
||||||
|
|
||||||
profile:
|
profile: # 存储 select 选择记录
|
||||||
# 存储 select 选择记录
|
|
||||||
store-selected: false
|
store-selected: false
|
||||||
|
|
||||||
# 持久化 fake-ip
|
# 持久化 fake-ip
|
||||||
@ -167,7 +169,7 @@ dns:
|
|||||||
prefer-h3: true # 开启 DoH 支持 HTTP/3,将并发尝试
|
prefer-h3: true # 开启 DoH 支持 HTTP/3,将并发尝试
|
||||||
listen: 0.0.0.0:53 # 开启 DNS 服务器监听
|
listen: 0.0.0.0:53 # 开启 DNS 服务器监听
|
||||||
# ipv6: false # false 将返回 AAAA 的空结果
|
# ipv6: false # false 将返回 AAAA 的空结果
|
||||||
|
# ipv6-timeout: 300 # 单位:ms,内部双栈并发时,向上游查询 AAAA 时,等待 AAAA 的时间,默认 100ms
|
||||||
# 用于解析 nameserver,fallback 以及其他DNS服务器配置的,DNS 服务域名
|
# 用于解析 nameserver,fallback 以及其他DNS服务器配置的,DNS 服务域名
|
||||||
# 只能使用纯 IP 地址,可使用加密 DNS
|
# 只能使用纯 IP 地址,可使用加密 DNS
|
||||||
default-nameserver:
|
default-nameserver:
|
||||||
@ -229,12 +231,13 @@ dns:
|
|||||||
# - '+.youtube.com'
|
# - '+.youtube.com'
|
||||||
|
|
||||||
# 配置查询域名使用的 DNS 服务器
|
# 配置查询域名使用的 DNS 服务器
|
||||||
nameserver-policy: # 'www.baidu.com': '114.114.114.114'
|
nameserver-policy:
|
||||||
|
# 'www.baidu.com': '114.114.114.114'
|
||||||
# '+.internal.crop.com': '10.0.0.1'
|
# '+.internal.crop.com': '10.0.0.1'
|
||||||
"geosite:cn":
|
"geosite:cn,private,apple":
|
||||||
- https://doh.pub/dns-query
|
- https://doh.pub/dns-query
|
||||||
- https://dns.alidns.com/dns-query
|
- https://dns.alidns.com/dns-query
|
||||||
"www.baidu.com": [https://doh.pub/dns-query, https://dns.alidns.com/dns-query]
|
"www.baidu.com,+.google.cn": [223.5.5.5, https://dns.alidns.com/dns-query]
|
||||||
|
|
||||||
proxies: # socks5
|
proxies: # socks5
|
||||||
- name: "socks"
|
- name: "socks"
|
||||||
@ -472,12 +475,44 @@ proxies: # socks5
|
|||||||
network: tcp
|
network: tcp
|
||||||
tls: true
|
tls: true
|
||||||
udp: true
|
udp: true
|
||||||
xudp: true
|
flow: xtls-rprx-vision
|
||||||
flow: xtls-rprx-vision # xtls-rprx-origin # enable XTLS
|
|
||||||
client-fingerprint: chrome
|
client-fingerprint: chrome
|
||||||
# fingerprint: xxxx
|
# fingerprint: xxxx
|
||||||
# skip-cert-verify: true
|
# skip-cert-verify: true
|
||||||
|
|
||||||
|
- name: "vless-reality-vision"
|
||||||
|
type: vless
|
||||||
|
server: server
|
||||||
|
port: 443
|
||||||
|
uuid: uuid
|
||||||
|
network: tcp
|
||||||
|
tls: true
|
||||||
|
udp: true
|
||||||
|
flow: xtls-rprx-vision
|
||||||
|
servername: www.microsoft.com # REALITY servername
|
||||||
|
reality-opts:
|
||||||
|
public-key: xxx
|
||||||
|
short-id: xxx # optional
|
||||||
|
client-fingerprint: chrome # cannot be empty
|
||||||
|
|
||||||
|
- name: "vless-reality-grpc"
|
||||||
|
type: vless
|
||||||
|
server: server
|
||||||
|
port: 443
|
||||||
|
uuid: uuid
|
||||||
|
network: grpc
|
||||||
|
tls: true
|
||||||
|
udp: true
|
||||||
|
flow:
|
||||||
|
# skip-cert-verify: true
|
||||||
|
client-fingerprint: chrome
|
||||||
|
servername: testingcf.jsdelivr.net
|
||||||
|
grpc-opts:
|
||||||
|
grpc-service-name: "grpc"
|
||||||
|
reality-opts:
|
||||||
|
public-key: CrrQSjAG_YkHLwvM2M-7XkKJilgL5upBKCp0od0tLhE
|
||||||
|
short-id: 10f897e26c4b9478
|
||||||
|
|
||||||
- name: "vless-ws"
|
- name: "vless-ws"
|
||||||
type: vless
|
type: vless
|
||||||
server: server
|
server: server
|
||||||
|
15
go.mod
15
go.mod
@ -18,19 +18,19 @@ require (
|
|||||||
github.com/jpillora/backoff v1.0.0
|
github.com/jpillora/backoff v1.0.0
|
||||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
||||||
github.com/mdlayher/netlink v1.7.2-0.20221213171556-9881fafed8c7
|
github.com/mdlayher/netlink v1.7.2-0.20221213171556-9881fafed8c7
|
||||||
github.com/metacubex/quic-go v0.32.0
|
github.com/metacubex/quic-go v0.33.1
|
||||||
github.com/metacubex/sing-shadowsocks v0.1.1-0.20230226153717-4e80da7e6947
|
github.com/metacubex/sing-shadowsocks v0.1.1-0.20230226153717-4e80da7e6947
|
||||||
github.com/metacubex/sing-tun v0.1.1-0.20230304153753-5058534177f3
|
github.com/metacubex/sing-tun v0.1.1-0.20230304153753-5058534177f3
|
||||||
github.com/metacubex/sing-wireguard v0.0.0-20230213124601-d04406a109b4
|
github.com/metacubex/sing-wireguard v0.0.0-20230310035749-f7595fcae5cb
|
||||||
github.com/miekg/dns v1.1.50
|
github.com/miekg/dns v1.1.50
|
||||||
github.com/mroth/weightedrand/v2 v2.0.0
|
github.com/mroth/weightedrand/v2 v2.0.0
|
||||||
github.com/oschwald/geoip2-golang v1.8.0
|
github.com/oschwald/geoip2-golang v1.8.0
|
||||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97
|
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97
|
||||||
github.com/sagernet/sing v0.1.8-0.20230303052048-c875a4ffab1a
|
github.com/sagernet/sing v0.1.8
|
||||||
github.com/sagernet/sing-shadowtls v0.1.0
|
github.com/sagernet/sing-shadowtls v0.1.0
|
||||||
github.com/sagernet/sing-vmess v0.1.3-0.20230303082804-627cc46ae68b
|
github.com/sagernet/sing-vmess v0.1.3-0.20230307060529-d110e81a50bc
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d
|
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d
|
||||||
github.com/sagernet/utls v0.0.0-20230220130002-c08891932056
|
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c
|
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c
|
||||||
github.com/samber/lo v1.37.0
|
github.com/samber/lo v1.37.0
|
||||||
github.com/sirupsen/logrus v1.9.0
|
github.com/sirupsen/logrus v1.9.0
|
||||||
@ -70,9 +70,8 @@ require (
|
|||||||
github.com/oschwald/maxminddb-golang v1.10.0 // indirect
|
github.com/oschwald/maxminddb-golang v1.10.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/quic-go/qpack v0.4.0 // indirect
|
github.com/quic-go/qpack v0.4.0 // indirect
|
||||||
github.com/quic-go/qtls-go1-18 v0.2.0 // indirect
|
github.com/quic-go/qtls-go1-19 v0.2.1 // indirect
|
||||||
github.com/quic-go/qtls-go1-19 v0.2.0 // indirect
|
github.com/quic-go/qtls-go1-20 v0.1.1 // indirect
|
||||||
github.com/quic-go/qtls-go1-20 v0.1.0 // indirect
|
|
||||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
|
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
|
||||||
github.com/u-root/uio v0.0.0-20221213070652-c3537552635f // indirect
|
github.com/u-root/uio v0.0.0-20221213070652-c3537552635f // indirect
|
||||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||||
|
30
go.sum
30
go.sum
@ -91,14 +91,14 @@ github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw
|
|||||||
github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc=
|
github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc=
|
||||||
github.com/metacubex/gvisor v0.0.0-20230304153416-e2bb9c726005 h1:0TEvReK/D6YLszjGj/bdx4d7amQSjQ2X/98r4ZiUbxU=
|
github.com/metacubex/gvisor v0.0.0-20230304153416-e2bb9c726005 h1:0TEvReK/D6YLszjGj/bdx4d7amQSjQ2X/98r4ZiUbxU=
|
||||||
github.com/metacubex/gvisor v0.0.0-20230304153416-e2bb9c726005/go.mod h1:wqEuzdImyqD2MCGE8CYRJXbB77oSEJeoSSXXdwKjnsE=
|
github.com/metacubex/gvisor v0.0.0-20230304153416-e2bb9c726005/go.mod h1:wqEuzdImyqD2MCGE8CYRJXbB77oSEJeoSSXXdwKjnsE=
|
||||||
github.com/metacubex/quic-go v0.32.0 h1:dSD8LB4MSeBuD4otd8y1DUZcRdDcEB0Ax5esPOqn2Hw=
|
github.com/metacubex/quic-go v0.33.1 h1:ZIxZFGivpSLOEZuuNkLy+aPvo1RP4uRBjNg3SAkXwIg=
|
||||||
github.com/metacubex/quic-go v0.32.0/go.mod h1:yParIzDYUd/t/pzFlDtZKhnvSqbUu0bPChlKEGmJStA=
|
github.com/metacubex/quic-go v0.33.1/go.mod h1:9nOiGX6kqV3+ZbkDKdTNzdFD726QQHPH6WDb36jUSpA=
|
||||||
github.com/metacubex/sing-shadowsocks v0.1.1-0.20230226153717-4e80da7e6947 h1:NnjC2+aIiyzzvFlo+C2WzBOJdsp+HAtu18FZomqYhUE=
|
github.com/metacubex/sing-shadowsocks v0.1.1-0.20230226153717-4e80da7e6947 h1:NnjC2+aIiyzzvFlo+C2WzBOJdsp+HAtu18FZomqYhUE=
|
||||||
github.com/metacubex/sing-shadowsocks v0.1.1-0.20230226153717-4e80da7e6947/go.mod h1:U2gwhxzqgbhKCgn2B4z3t0Cj0LpMWFl/02BGCoG421w=
|
github.com/metacubex/sing-shadowsocks v0.1.1-0.20230226153717-4e80da7e6947/go.mod h1:U2gwhxzqgbhKCgn2B4z3t0Cj0LpMWFl/02BGCoG421w=
|
||||||
github.com/metacubex/sing-tun v0.1.1-0.20230304153753-5058534177f3 h1:oQLThm1a8E7hHmoM9XF2cO0FZPsHVynC4YXW4b3liUI=
|
github.com/metacubex/sing-tun v0.1.1-0.20230304153753-5058534177f3 h1:oQLThm1a8E7hHmoM9XF2cO0FZPsHVynC4YXW4b3liUI=
|
||||||
github.com/metacubex/sing-tun v0.1.1-0.20230304153753-5058534177f3/go.mod h1:b/19bRRhwampNPV+1gVDyDsQHmuGDaplxPQkwJh1kj4=
|
github.com/metacubex/sing-tun v0.1.1-0.20230304153753-5058534177f3/go.mod h1:b/19bRRhwampNPV+1gVDyDsQHmuGDaplxPQkwJh1kj4=
|
||||||
github.com/metacubex/sing-wireguard v0.0.0-20230213124601-d04406a109b4 h1:d96mCF/LYyC9kULd2xwcXfP0Jd8klrOngmRxuUIZg/8=
|
github.com/metacubex/sing-wireguard v0.0.0-20230310035749-f7595fcae5cb h1:uhvzbtOvyg2c1k1H2EeVPuPvTEjDHCq4+U0AljG40P8=
|
||||||
github.com/metacubex/sing-wireguard v0.0.0-20230213124601-d04406a109b4/go.mod h1:p2VpJuxRefgVMxc8cmatMGSFNvYbjMYMsXJOe7qFstw=
|
github.com/metacubex/sing-wireguard v0.0.0-20230310035749-f7595fcae5cb/go.mod h1:7mPG9qYln+CLKBcDt7Dk4c7b3S53VzEfexMVPe6T6FM=
|
||||||
github.com/metacubex/uber-atomic v0.0.0-20230202125923-feb10b770370 h1:UkViS4DCESAUEYgbIEQdD02hyMacFt6Dny+1MOJtNIo=
|
github.com/metacubex/uber-atomic v0.0.0-20230202125923-feb10b770370 h1:UkViS4DCESAUEYgbIEQdD02hyMacFt6Dny+1MOJtNIo=
|
||||||
github.com/metacubex/uber-atomic v0.0.0-20230202125923-feb10b770370/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
github.com/metacubex/uber-atomic v0.0.0-20230202125923-feb10b770370/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||||
@ -117,28 +117,26 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
|||||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||||
github.com/quic-go/qtls-go1-18 v0.2.0 h1:5ViXqBZ90wpUcZS0ge79rf029yx0dYB0McyPJwqqj7U=
|
github.com/quic-go/qtls-go1-19 v0.2.1 h1:aJcKNMkH5ASEJB9FXNeZCyTEIHU1J7MmHyz1Q1TSG1A=
|
||||||
github.com/quic-go/qtls-go1-18 v0.2.0/go.mod h1:moGulGHK7o6O8lSPSZNoOwcLvJKJ85vVNc7oJFD65bc=
|
github.com/quic-go/qtls-go1-19 v0.2.1/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
||||||
github.com/quic-go/qtls-go1-19 v0.2.0 h1:Cvn2WdhyViFUHoOqK52i51k4nDX8EwIh5VJiVM4nttk=
|
github.com/quic-go/qtls-go1-20 v0.1.1 h1:KbChDlg82d3IHqaj2bn6GfKRj84Per2VGf5XV3wSwQk=
|
||||||
github.com/quic-go/qtls-go1-19 v0.2.0/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
github.com/quic-go/qtls-go1-20 v0.1.1/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
||||||
github.com/quic-go/qtls-go1-20 v0.1.0 h1:d1PK3ErFy9t7zxKsG3NXBJXZjp/kMLoIb3y/kV54oAI=
|
|
||||||
github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
|
||||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
|
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
|
||||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
||||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
||||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||||
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||||
github.com/sagernet/sing v0.1.8-0.20230303052048-c875a4ffab1a h1:NvhI/8DMFt2yV3eoYhw6P/XyWzzIKkMiGvFglJbWHWg=
|
github.com/sagernet/sing v0.1.8 h1:6DKo2FkSHn0nUcjO7bAext/ai7y7pCusK/+fScBJ5Jk=
|
||||||
github.com/sagernet/sing v0.1.8-0.20230303052048-c875a4ffab1a/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
|
github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
|
||||||
github.com/sagernet/sing-shadowtls v0.1.0 h1:05MYce8aR5xfKIn+y7xRFsdKhKt44QZTSEQW+lG5IWQ=
|
github.com/sagernet/sing-shadowtls v0.1.0 h1:05MYce8aR5xfKIn+y7xRFsdKhKt44QZTSEQW+lG5IWQ=
|
||||||
github.com/sagernet/sing-shadowtls v0.1.0/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc=
|
github.com/sagernet/sing-shadowtls v0.1.0/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc=
|
||||||
github.com/sagernet/sing-vmess v0.1.3-0.20230303082804-627cc46ae68b h1:NZeF0ATeJwe4W3gTJNeIfTB6yBxai665q1HvDOaWmmU=
|
github.com/sagernet/sing-vmess v0.1.3-0.20230307060529-d110e81a50bc h1:vqlYWupvVDRpvv2F+RtECJN+VbuKjLtmQculQvOecls=
|
||||||
github.com/sagernet/sing-vmess v0.1.3-0.20230303082804-627cc46ae68b/go.mod h1:9NSj8mZTx1JIY/HF9LaYRppUsVkysIN5tEFpNZujXxY=
|
github.com/sagernet/sing-vmess v0.1.3-0.20230307060529-d110e81a50bc/go.mod h1:V14iffGwhZPU2S7wgIiPlLWXygSjAXazYzD1w0ejBl4=
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d h1:trP/l6ZPWvQ/5Gv99Z7/t/v8iYy06akDMejxW1sznUk=
|
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d h1:trP/l6ZPWvQ/5Gv99Z7/t/v8iYy06akDMejxW1sznUk=
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d/go.mod h1:jk6Ii8Y3En+j2KQDLgdgQGwb3M6y7EL567jFnGYhN9g=
|
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d/go.mod h1:jk6Ii8Y3En+j2KQDLgdgQGwb3M6y7EL567jFnGYhN9g=
|
||||||
github.com/sagernet/utls v0.0.0-20230220130002-c08891932056 h1:gDXi/0uYe8dA48UyUI1LM2la5QYN0IvsDvR2H2+kFnA=
|
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 h1:kDUqhc9Vsk5HJuhfIATJ8oQwBmpOZJuozQG7Vk88lL4=
|
||||||
github.com/sagernet/utls v0.0.0-20230220130002-c08891932056/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
|
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI=
|
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI=
|
||||||
github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw=
|
github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw=
|
||||||
|
@ -190,6 +190,7 @@ func updateDNS(c *config.DNS, generalIPv6 bool) {
|
|||||||
Main: c.NameServer,
|
Main: c.NameServer,
|
||||||
Fallback: c.Fallback,
|
Fallback: c.Fallback,
|
||||||
IPv6: c.IPv6 && generalIPv6,
|
IPv6: c.IPv6 && generalIPv6,
|
||||||
|
IPv6Timeout: c.IPv6Timeout,
|
||||||
EnhancedMode: c.EnhancedMode,
|
EnhancedMode: c.EnhancedMode,
|
||||||
Pool: c.FakeIPRange,
|
Pool: c.FakeIPRange,
|
||||||
Hosts: c.Hosts,
|
Hosts: c.Hosts,
|
||||||
@ -225,8 +226,8 @@ func updateDNS(c *config.DNS, generalIPv6 bool) {
|
|||||||
dns.ReCreateServer(c.Listen, r, m)
|
dns.ReCreateServer(c.Listen, r, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateHosts(tree *trie.DomainTrie[netip.Addr]) {
|
func updateHosts(tree *trie.DomainTrie[resolver.HostValue]) {
|
||||||
resolver.DefaultHosts = tree
|
resolver.DefaultHosts = resolver.NewHosts(tree)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateProxies(proxies map[string]C.Proxy, providers map[string]provider.ProxyProvider) {
|
func updateProxies(proxies map[string]C.Proxy, providers map[string]provider.ProxyProvider) {
|
||||||
|
@ -44,7 +44,7 @@ func Parse(options ...Option) error {
|
|||||||
|
|
||||||
if cfg.General.ExternalController != "" {
|
if cfg.General.ExternalController != "" {
|
||||||
go route.Start(cfg.General.ExternalController, cfg.General.ExternalControllerTLS,
|
go route.Start(cfg.General.ExternalController, cfg.General.ExternalControllerTLS,
|
||||||
cfg.General.Secret, cfg.TLS.Certificate, cfg.TLS.PrivateKey,cfg.General.LogLevel==log.DEBUG)
|
cfg.General.Secret, cfg.TLS.Certificate, cfg.TLS.PrivateKey, cfg.General.LogLevel == log.DEBUG)
|
||||||
}
|
}
|
||||||
|
|
||||||
executor.ApplyConfig(cfg, true)
|
executor.ApplyConfig(cfg, true)
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/common/buf"
|
"github.com/Dreamacro/clash/common/buf"
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
|
|
||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
@ -189,7 +190,7 @@ func (g *Conn) SetDeadline(t time.Time) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, Fingerprint string) *TransportWrap {
|
func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, Fingerprint string, realityConfig *tlsC.RealityConfig) *TransportWrap {
|
||||||
wrap := TransportWrap{}
|
wrap := TransportWrap{}
|
||||||
|
|
||||||
dialFunc := func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
|
dialFunc := func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
|
||||||
@ -201,20 +202,37 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, Fingerprint string) *T
|
|||||||
wrap.remoteAddr = pconn.RemoteAddr()
|
wrap.remoteAddr = pconn.RemoteAddr()
|
||||||
|
|
||||||
if len(Fingerprint) != 0 {
|
if len(Fingerprint) != 0 {
|
||||||
if fingerprint, exists := tlsC.GetFingerprint(Fingerprint); exists {
|
if realityConfig == nil {
|
||||||
utlsConn := tlsC.UClient(pconn, cfg, fingerprint)
|
if fingerprint, exists := tlsC.GetFingerprint(Fingerprint); exists {
|
||||||
if err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx); err != nil {
|
utlsConn := tlsC.UClient(pconn, cfg, fingerprint)
|
||||||
|
if err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx); err != nil {
|
||||||
|
pconn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
state := utlsConn.(*tlsC.UConn).ConnectionState()
|
||||||
|
if p := state.NegotiatedProtocol; p != http2.NextProtoTLS {
|
||||||
|
utlsConn.Close()
|
||||||
|
return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS)
|
||||||
|
}
|
||||||
|
return utlsConn, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
realityConn, err := tlsC.GetRealityConn(ctx, pconn, Fingerprint, cfg, realityConfig)
|
||||||
|
if err != nil {
|
||||||
pconn.Close()
|
pconn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
state := utlsConn.(*tlsC.UConn).ConnectionState()
|
//state := realityConn.(*utls.UConn).ConnectionState()
|
||||||
if p := state.NegotiatedProtocol; p != http2.NextProtoTLS {
|
//if p := state.NegotiatedProtocol; p != http2.NextProtoTLS {
|
||||||
utlsConn.Close()
|
// realityConn.Close()
|
||||||
return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS)
|
// return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS)
|
||||||
}
|
//}
|
||||||
return utlsConn, nil
|
return realityConn, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if realityConfig != nil {
|
||||||
|
return nil, errors.New("REALITY is based on uTLS, please set a client-fingerprint")
|
||||||
|
}
|
||||||
|
|
||||||
conn := tls.Client(pconn, cfg)
|
conn := tls.Client(pconn, cfg)
|
||||||
if err := conn.HandshakeContext(ctx); err != nil {
|
if err := conn.HandshakeContext(ctx); err != nil {
|
||||||
@ -274,11 +292,11 @@ func StreamGunWithTransport(transport *TransportWrap, cfg *Config) (net.Conn, er
|
|||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func StreamGunWithConn(conn net.Conn, tlsConfig *tls.Config, cfg *Config) (net.Conn, error) {
|
func StreamGunWithConn(conn net.Conn, tlsConfig *tls.Config, cfg *Config, realityConfig *tlsC.RealityConfig) (net.Conn, error) {
|
||||||
dialFn := func(network, addr string) (net.Conn, error) {
|
dialFn := func(network, addr string) (net.Conn, error) {
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
transport := NewHTTP2Client(dialFn, tlsConfig, cfg.ClientFingerprint)
|
transport := NewHTTP2Client(dialFn, tlsConfig, cfg.ClientFingerprint, realityConfig)
|
||||||
return StreamGunWithTransport(transport, cfg)
|
return StreamGunWithTransport(transport, cfg)
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
"github.com/Dreamacro/clash/transport/vless"
|
"github.com/Dreamacro/clash/transport/vless"
|
||||||
"github.com/Dreamacro/clash/transport/vmess"
|
"github.com/Dreamacro/clash/transport/vmess"
|
||||||
|
|
||||||
xtls "github.com/xtls/go"
|
xtls "github.com/xtls/go"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -54,6 +55,7 @@ type Option struct {
|
|||||||
Flow string
|
Flow string
|
||||||
FlowShow bool
|
FlowShow bool
|
||||||
ClientFingerprint string
|
ClientFingerprint string
|
||||||
|
Reality *tlsC.RealityConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebsocketOption struct {
|
type WebsocketOption struct {
|
||||||
@ -117,16 +119,24 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(t.option.ClientFingerprint) != 0 {
|
if len(t.option.ClientFingerprint) != 0 {
|
||||||
utlsConn, valid := vmess.GetUtlsConnWithClientFingerprint(conn, t.option.ClientFingerprint, tlsConfig)
|
if t.option.Reality == nil {
|
||||||
if valid {
|
utlsConn, valid := vmess.GetUTLSConn(conn, t.option.ClientFingerprint, tlsConfig)
|
||||||
|
if valid {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx)
|
||||||
|
return utlsConn, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
return tlsC.GetRealityConn(ctx, conn, t.option.ClientFingerprint, tlsConfig, t.option.Reality)
|
||||||
err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx)
|
|
||||||
return utlsConn, err
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if t.option.Reality != nil {
|
||||||
|
return nil, errors.New("REALITY is based on uTLS, please set a client-fingerprint")
|
||||||
|
}
|
||||||
|
|
||||||
tlsConn := tls.Client(conn, tlsConfig)
|
tlsConn := tls.Client(conn, tlsConfig)
|
||||||
|
|
||||||
|
@ -200,7 +200,7 @@ func (q *quicStreamPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (q *quicStreamPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
func (q *quicStreamPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||||
if len(p) > q.maxUdpRelayPacketSize {
|
if q.udpRelayMode != "quic" && len(p) > q.maxUdpRelayPacketSize {
|
||||||
return 0, fmt.Errorf("udp packet too large(%d > %d)", len(p), q.maxUdpRelayPacketSize)
|
return 0, fmt.Errorf("udp packet too large(%d > %d)", len(p), q.maxUdpRelayPacketSize)
|
||||||
}
|
}
|
||||||
if q.closed {
|
if q.closed {
|
||||||
@ -215,7 +215,6 @@ func (q *quicStreamPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err erro
|
|||||||
q.deferQuicConnFn(q.quicConn, err)
|
q.deferQuicConnFn(q.quicConn, err)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
addr.String()
|
|
||||||
buf := pool.GetBuffer()
|
buf := pool.GetBuffer()
|
||||||
defer pool.PutBuffer(buf)
|
defer pool.PutBuffer(buf)
|
||||||
addrPort, err := netip.ParseAddrPort(addr.String())
|
addrPort, err := netip.ParseAddrPort(addr.String())
|
||||||
@ -239,7 +238,8 @@ func (q *quicStreamPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err erro
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
default: // native
|
default: // native
|
||||||
err = q.quicConn.SendMessage(buf.Bytes())
|
data := buf.Bytes()
|
||||||
|
err = q.quicConn.SendMessage(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -250,7 +250,29 @@ func (q *quicStreamPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (q *quicStreamPacketConn) LocalAddr() net.Addr {
|
func (q *quicStreamPacketConn) LocalAddr() net.Addr {
|
||||||
return q.quicConn.LocalAddr()
|
addr := q.quicConn.LocalAddr()
|
||||||
|
if q.inputConn != nil { // client
|
||||||
|
return &packetAddr{addrStr: q.quicConn.LocalAddr().String(), connId: q.connId, rawAddr: addr}
|
||||||
|
}
|
||||||
|
return addr // server
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ net.PacketConn = &quicStreamPacketConn{}
|
var _ net.PacketConn = &quicStreamPacketConn{}
|
||||||
|
|
||||||
|
type packetAddr struct {
|
||||||
|
addrStr string
|
||||||
|
connId uint32
|
||||||
|
rawAddr net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a packetAddr) Network() string {
|
||||||
|
return "tuic"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a packetAddr) String() string {
|
||||||
|
return fmt.Sprintf("%s-%d", a.addrStr, a.connId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a packetAddr) RawAddr() net.Addr {
|
||||||
|
return a.rawAddr
|
||||||
|
}
|
||||||
|
@ -114,9 +114,6 @@ func NewAuthenticate(TKN [32]byte) Authenticate {
|
|||||||
|
|
||||||
func ReadAuthenticateWithHead(head CommandHead, reader BufferedReader) (c Authenticate, err error) {
|
func ReadAuthenticateWithHead(head CommandHead, reader BufferedReader) (c Authenticate, err error) {
|
||||||
c.CommandHead = head
|
c.CommandHead = head
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if c.CommandHead.TYPE != AuthenticateType {
|
if c.CommandHead.TYPE != AuthenticateType {
|
||||||
err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE)
|
err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE)
|
||||||
return
|
return
|
||||||
@ -170,9 +167,6 @@ func NewConnect(ADDR Address) Connect {
|
|||||||
|
|
||||||
func ReadConnectWithHead(head CommandHead, reader BufferedReader) (c Connect, err error) {
|
func ReadConnectWithHead(head CommandHead, reader BufferedReader) (c Connect, err error) {
|
||||||
c.CommandHead = head
|
c.CommandHead = head
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if c.CommandHead.TYPE != ConnectType {
|
if c.CommandHead.TYPE != ConnectType {
|
||||||
err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE)
|
err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE)
|
||||||
return
|
return
|
||||||
@ -228,9 +222,6 @@ func NewPacket(ASSOC_ID uint32, LEN uint16, ADDR Address, DATA []byte) Packet {
|
|||||||
|
|
||||||
func ReadPacketWithHead(head CommandHead, reader BufferedReader) (c Packet, err error) {
|
func ReadPacketWithHead(head CommandHead, reader BufferedReader) (c Packet, err error) {
|
||||||
c.CommandHead = head
|
c.CommandHead = head
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if c.CommandHead.TYPE != PacketType {
|
if c.CommandHead.TYPE != PacketType {
|
||||||
err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE)
|
err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE)
|
||||||
return
|
return
|
||||||
@ -305,9 +296,6 @@ func NewDissociate(ASSOC_ID uint32) Dissociate {
|
|||||||
|
|
||||||
func ReadDissociateWithHead(head CommandHead, reader BufferedReader) (c Dissociate, err error) {
|
func ReadDissociateWithHead(head CommandHead, reader BufferedReader) (c Dissociate, err error) {
|
||||||
c.CommandHead = head
|
c.CommandHead = head
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if c.CommandHead.TYPE != DissociateType {
|
if c.CommandHead.TYPE != DissociateType {
|
||||||
err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE)
|
err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE)
|
||||||
return
|
return
|
||||||
@ -476,15 +464,17 @@ func NewAddress(metadata *C.Metadata) Address {
|
|||||||
|
|
||||||
func NewAddressAddrPort(addrPort netip.AddrPort) Address {
|
func NewAddressAddrPort(addrPort netip.AddrPort) Address {
|
||||||
var addrType byte
|
var addrType byte
|
||||||
if addrPort.Addr().Is4() {
|
port := addrPort.Port()
|
||||||
|
addr := addrPort.Addr().Unmap()
|
||||||
|
if addr.Is4() {
|
||||||
addrType = AtypIPv4
|
addrType = AtypIPv4
|
||||||
} else {
|
} else {
|
||||||
addrType = AtypIPv6
|
addrType = AtypIPv6
|
||||||
}
|
}
|
||||||
return Address{
|
return Address{
|
||||||
TYPE: addrType,
|
TYPE: addrType,
|
||||||
ADDR: addrPort.Addr().AsSlice(),
|
ADDR: addr.AsSlice(),
|
||||||
PORT: addrPort.Port(),
|
PORT: port,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@ -76,7 +75,7 @@ func (s *Server) Close() error {
|
|||||||
|
|
||||||
type serverHandler struct {
|
type serverHandler struct {
|
||||||
*Server
|
*Server
|
||||||
quicConn quic.Connection
|
quicConn quic.EarlyConnection
|
||||||
uuid uuid.UUID
|
uuid uuid.UUID
|
||||||
|
|
||||||
authCh chan struct{}
|
authCh chan struct{}
|
||||||
@ -87,13 +86,6 @@ type serverHandler struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *serverHandler) handle() {
|
func (s *serverHandler) handle() {
|
||||||
time.AfterFunc(s.AuthenticationTimeout, func() {
|
|
||||||
s.authOnce.Do(func() {
|
|
||||||
_ = s.quicConn.CloseWithError(AuthenticationTimeout, "")
|
|
||||||
s.authOk = false
|
|
||||||
close(s.authCh)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
go func() {
|
go func() {
|
||||||
_ = s.handleUniStream()
|
_ = s.handleUniStream()
|
||||||
}()
|
}()
|
||||||
@ -103,6 +95,15 @@ func (s *serverHandler) handle() {
|
|||||||
go func() {
|
go func() {
|
||||||
_ = s.handleMessage()
|
_ = s.handleMessage()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
<-s.quicConn.HandshakeComplete().Done()
|
||||||
|
time.AfterFunc(s.AuthenticationTimeout, func() {
|
||||||
|
s.authOnce.Do(func() {
|
||||||
|
_ = s.quicConn.CloseWithError(AuthenticationTimeout, "AuthenticationTimeout")
|
||||||
|
s.authOk = false
|
||||||
|
close(s.authCh)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *serverHandler) handleMessage() (err error) {
|
func (s *serverHandler) handleMessage() (err error) {
|
||||||
@ -152,14 +153,10 @@ func (s *serverHandler) parsePacket(packet Packet, udpRelayMode string) (err err
|
|||||||
return s.HandleUdpFn(packet.ADDR.SocksAddr(), &serverUDPPacket{
|
return s.HandleUdpFn(packet.ADDR.SocksAddr(), &serverUDPPacket{
|
||||||
pc: pc,
|
pc: pc,
|
||||||
packet: &packet,
|
packet: &packet,
|
||||||
rAddr: s.genServerAssocIdAddr(assocId, s.quicConn.RemoteAddr()),
|
rAddr: &packetAddr{addrStr: "tuic-" + s.uuid.String(), connId: assocId, rawAddr: s.quicConn.RemoteAddr()},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *serverHandler) genServerAssocIdAddr(assocId uint32, addr net.Addr) net.Addr {
|
|
||||||
return &ServerAssocIdAddr{assocId: fmt.Sprintf("tuic-%s-%d", s.uuid.String(), assocId), addr: addr}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *serverHandler) handleStream() (err error) {
|
func (s *serverHandler) handleStream() (err error) {
|
||||||
for {
|
for {
|
||||||
var quicStream quic.Stream
|
var quicStream quic.Stream
|
||||||
@ -239,7 +236,7 @@ func (s *serverHandler) handleUniStream() (err error) {
|
|||||||
}
|
}
|
||||||
s.authOnce.Do(func() {
|
s.authOnce.Do(func() {
|
||||||
if !ok {
|
if !ok {
|
||||||
_ = s.quicConn.CloseWithError(AuthenticationFailed, "")
|
_ = s.quicConn.CloseWithError(AuthenticationFailed, "AuthenticationFailed")
|
||||||
}
|
}
|
||||||
s.authOk = ok
|
s.authOk = ok
|
||||||
close(s.authCh)
|
close(s.authCh)
|
||||||
@ -274,23 +271,6 @@ func (s *serverHandler) handleUniStream() (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServerAssocIdAddr struct {
|
|
||||||
assocId string
|
|
||||||
addr net.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a ServerAssocIdAddr) Network() string {
|
|
||||||
return "ServerAssocIdAddr"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a ServerAssocIdAddr) String() string {
|
|
||||||
return a.assocId
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a ServerAssocIdAddr) RawAddr() net.Addr {
|
|
||||||
return a.addr
|
|
||||||
}
|
|
||||||
|
|
||||||
type serverUDPPacket struct {
|
type serverUDPPacket struct {
|
||||||
pc *quicStreamPacketConn
|
pc *quicStreamPacketConn
|
||||||
packet *Packet
|
packet *Packet
|
||||||
|
@ -3,6 +3,7 @@ package vless
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/buf"
|
"github.com/Dreamacro/clash/common/buf"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
|
|
||||||
@ -21,16 +22,15 @@ const (
|
|||||||
func WriteWithPadding(buffer *buf.Buffer, p []byte, command byte, userUUID *uuid.UUID, paddingTLS bool) {
|
func WriteWithPadding(buffer *buf.Buffer, p []byte, command byte, userUUID *uuid.UUID, paddingTLS bool) {
|
||||||
contentLen := int32(len(p))
|
contentLen := int32(len(p))
|
||||||
var paddingLen int32
|
var paddingLen int32
|
||||||
if contentLen < 900 && paddingTLS {
|
if contentLen < 900 {
|
||||||
log.Debugln("long padding")
|
if paddingTLS {
|
||||||
paddingLen = fastrand.Int31n(500) + 900 - contentLen
|
//log.Debugln("long padding")
|
||||||
} else {
|
paddingLen = fastrand.Int31n(500) + 900 - contentLen
|
||||||
paddingLen = fastrand.Int31n(256)
|
} else {
|
||||||
|
paddingLen = fastrand.Int31n(256)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if paddingLen > buf.BufferSize-21-contentLen {
|
if userUUID != nil {
|
||||||
paddingLen = buf.BufferSize - 21 - contentLen
|
|
||||||
}
|
|
||||||
if userUUID != nil { // unnecessary, but keep the same with Xray
|
|
||||||
buffer.Write(userUUID.Bytes())
|
buffer.Write(userUUID.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,6 +38,7 @@ func WriteWithPadding(buffer *buf.Buffer, p []byte, command byte, userUUID *uuid
|
|||||||
binary.BigEndian.PutUint16(buffer.Extend(2), uint16(contentLen))
|
binary.BigEndian.PutUint16(buffer.Extend(2), uint16(contentLen))
|
||||||
binary.BigEndian.PutUint16(buffer.Extend(2), uint16(paddingLen))
|
binary.BigEndian.PutUint16(buffer.Extend(2), uint16(paddingLen))
|
||||||
buffer.Write(p)
|
buffer.Write(p)
|
||||||
|
|
||||||
buffer.Extend(int(paddingLen))
|
buffer.Extend(int(paddingLen))
|
||||||
log.Debugln("XTLS Vision write padding1: command=%v, payloadLen=%v, paddingLen=%v", command, contentLen, paddingLen)
|
log.Debugln("XTLS Vision write padding1: command=%v, payloadLen=%v, paddingLen=%v", command, contentLen, paddingLen)
|
||||||
}
|
}
|
||||||
@ -45,21 +46,22 @@ func WriteWithPadding(buffer *buf.Buffer, p []byte, command byte, userUUID *uuid
|
|||||||
func ApplyPadding(buffer *buf.Buffer, command byte, userUUID *uuid.UUID, paddingTLS bool) {
|
func ApplyPadding(buffer *buf.Buffer, command byte, userUUID *uuid.UUID, paddingTLS bool) {
|
||||||
contentLen := int32(buffer.Len())
|
contentLen := int32(buffer.Len())
|
||||||
var paddingLen int32
|
var paddingLen int32
|
||||||
if contentLen < 900 && paddingTLS {
|
if contentLen < 900 {
|
||||||
log.Debugln("long padding")
|
if paddingTLS {
|
||||||
paddingLen = fastrand.Int31n(500) + 900 - contentLen
|
//log.Debugln("long padding")
|
||||||
} else {
|
paddingLen = fastrand.Int31n(500) + 900 - contentLen
|
||||||
paddingLen = fastrand.Int31n(256)
|
} else {
|
||||||
}
|
paddingLen = fastrand.Int31n(256)
|
||||||
if paddingLen > buf.BufferSize-21-contentLen {
|
}
|
||||||
paddingLen = buf.BufferSize - 21 - contentLen
|
|
||||||
}
|
}
|
||||||
|
|
||||||
binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(paddingLen))
|
binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(paddingLen))
|
||||||
binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(contentLen))
|
binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(contentLen))
|
||||||
buffer.ExtendHeader(1)[0] = command
|
buffer.ExtendHeader(1)[0] = command
|
||||||
if userUUID != nil { // unnecessary, but keep the same with Xray
|
if userUUID != nil {
|
||||||
copy(buffer.ExtendHeader(uuid.Size), userUUID.Bytes())
|
copy(buffer.ExtendHeader(uuid.Size), userUUID.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.Extend(int(paddingLen))
|
buffer.Extend(int(paddingLen))
|
||||||
log.Debugln("XTLS Vision write padding2: command=%d, payloadLen=%d, paddingLen=%d", command, contentLen, paddingLen)
|
log.Debugln("XTLS Vision write padding2: command=%d, payloadLen=%d, paddingLen=%d", command, contentLen, paddingLen)
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package vmess
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
@ -15,6 +16,7 @@ type TLSConfig struct {
|
|||||||
FingerPrint string
|
FingerPrint string
|
||||||
ClientFingerprint string
|
ClientFingerprint string
|
||||||
NextProtos []string
|
NextProtos []string
|
||||||
|
Reality *tlsC.RealityConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
|
func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
|
||||||
@ -34,15 +36,25 @@ func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(cfg.ClientFingerprint) != 0 {
|
if len(cfg.ClientFingerprint) != 0 {
|
||||||
utlsConn, valid := GetUtlsConnWithClientFingerprint(conn, cfg.ClientFingerprint, tlsConfig)
|
if cfg.Reality == nil {
|
||||||
if valid {
|
utlsConn, valid := GetUTLSConn(conn, cfg.ClientFingerprint, tlsConfig)
|
||||||
|
if valid {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx)
|
||||||
|
return utlsConn, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
return tlsC.GetRealityConn(ctx, conn, cfg.ClientFingerprint, tlsConfig, cfg.Reality)
|
||||||
err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx)
|
|
||||||
return utlsConn, err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if cfg.Reality != nil {
|
||||||
|
return nil, errors.New("REALITY is based on uTLS, please set a client-fingerprint")
|
||||||
|
}
|
||||||
|
|
||||||
tlsConn := tls.Client(conn, tlsConfig)
|
tlsConn := tls.Client(conn, tlsConfig)
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
||||||
@ -52,7 +64,7 @@ func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
|
|||||||
return tlsConn, err
|
return tlsConn, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUtlsConnWithClientFingerprint(conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config) (net.Conn, bool) {
|
func GetUTLSConn(conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config) (net.Conn, bool) {
|
||||||
|
|
||||||
if fingerprint, exists := tlsC.GetFingerprint(ClientFingerprint); exists {
|
if fingerprint, exists := tlsC.GetFingerprint(ClientFingerprint); exists {
|
||||||
utlsConn := tlsC.UClient(conn, tlsConfig, fingerprint)
|
utlsConn := tlsC.UClient(conn, tlsConfig, fingerprint)
|
||||||
|
@ -46,7 +46,8 @@ func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, oAddr,
|
|||||||
}
|
}
|
||||||
|
|
||||||
fromUDPAddr := from.(*net.UDPAddr)
|
fromUDPAddr := from.(*net.UDPAddr)
|
||||||
fromUDPAddr = &(*fromUDPAddr) // make a copy
|
_fromUDPAddr := *fromUDPAddr
|
||||||
|
fromUDPAddr = &_fromUDPAddr // make a copy
|
||||||
if fromAddr, ok := netip.AddrFromSlice(fromUDPAddr.IP); ok {
|
if fromAddr, ok := netip.AddrFromSlice(fromUDPAddr.IP); ok {
|
||||||
if fAddr.IsValid() && (oAddr.Unmap() == fromAddr.Unmap()) {
|
if fAddr.IsValid() && (oAddr.Unmap() == fromAddr.Unmap()) {
|
||||||
fromUDPAddr.IP = fAddr.Unmap().AsSlice()
|
fromUDPAddr.IP = fAddr.Unmap().AsSlice()
|
||||||
|
@ -201,13 +201,18 @@ func preHandleMetadata(metadata *C.Metadata) error {
|
|||||||
if resolver.FakeIPEnabled() {
|
if resolver.FakeIPEnabled() {
|
||||||
metadata.DstIP = netip.Addr{}
|
metadata.DstIP = netip.Addr{}
|
||||||
metadata.DNSMode = C.DNSFakeIP
|
metadata.DNSMode = C.DNSFakeIP
|
||||||
} else if node := resolver.DefaultHosts.Search(host); node != nil {
|
} else if node, ok := resolver.DefaultHosts.Search(host, false); ok {
|
||||||
// redir-host should lookup the hosts
|
// redir-host should lookup the hosts
|
||||||
metadata.DstIP = node.Data()
|
metadata.DstIP, _ = node.RandIP()
|
||||||
|
} else if node != nil && node.IsDomain {
|
||||||
|
metadata.Host = node.Domain
|
||||||
}
|
}
|
||||||
} else if resolver.IsFakeIP(metadata.DstIP) {
|
} else if resolver.IsFakeIP(metadata.DstIP) {
|
||||||
return fmt.Errorf("fake DNS record %s missing", metadata.DstIP)
|
return fmt.Errorf("fake DNS record %s missing", metadata.DstIP)
|
||||||
}
|
}
|
||||||
|
} else if node, ok := resolver.DefaultHosts.Search(metadata.Host, true); ok {
|
||||||
|
// try use domain mapping
|
||||||
|
metadata.Host = node.Domain
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -392,8 +397,8 @@ func handleTCPConn(connCtx C.ConnContext) {
|
|||||||
|
|
||||||
dialMetadata := metadata
|
dialMetadata := metadata
|
||||||
if len(metadata.Host) > 0 {
|
if len(metadata.Host) > 0 {
|
||||||
if node := resolver.DefaultHosts.Search(metadata.Host); node != nil {
|
if node, ok := resolver.DefaultHosts.Search(metadata.Host, false); ok {
|
||||||
if dstIp := node.Data(); !FakeIPRange().Contains(dstIp) {
|
if dstIp, _ := node.RandIP(); !FakeIPRange().Contains(dstIp) {
|
||||||
dialMetadata.DstIP = dstIp
|
dialMetadata.DstIP = dstIp
|
||||||
dialMetadata.DNSMode = C.DNSHosts
|
dialMetadata.DNSMode = C.DNSHosts
|
||||||
dialMetadata = dialMetadata.Pure()
|
dialMetadata = dialMetadata.Pure()
|
||||||
@ -498,8 +503,8 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
|
|||||||
processFound bool
|
processFound bool
|
||||||
)
|
)
|
||||||
|
|
||||||
if node := resolver.DefaultHosts.Search(metadata.Host); node != nil {
|
if node, ok := resolver.DefaultHosts.Search(metadata.Host, false); ok {
|
||||||
metadata.DstIP = node.Data()
|
metadata.DstIP, _ = node.RandIP()
|
||||||
resolved = true
|
resolved = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user