Compare commits
106 Commits
v1.14.2
...
dev-restls
Author | SHA1 | Date | |
---|---|---|---|
c330d2c82c | |||
6f159d0cac | |||
a35e40486b | |||
1bc3ecb027 | |||
c6a329281e | |||
13111081be | |||
5de043acc6 | |||
7d230139a0 | |||
0a6c848c9e | |||
074fee2b48 | |||
7f588935ea | |||
4b72ae7aab | |||
09b4a7ff15 | |||
7944522188 | |||
ae4d114802 | |||
07f3cd2ae5 | |||
035d878a9f | |||
913ed62095 | |||
8c135e4a91 | |||
5bcfe1a6c6 | |||
2ccef31f75 | |||
2c4783ff8b | |||
7cc1c1b561 | |||
d309c6311d | |||
e7f9072911 | |||
fe298bd53f | |||
6fe7f4641e | |||
9ae0bd9c2b | |||
dca98b7aa1 | |||
c0fc5d142f | |||
828cd6463b | |||
5ce6ac0a62 | |||
a973a6c7d2 | |||
805b3c4669 | |||
e2d590ca12 | |||
a454a7f736 | |||
bae61a8152 | |||
0c984644b4 | |||
921b2c3aa4 | |||
8ba7ce73d8 | |||
76a8fe3839 | |||
1e6f0f28f6 | |||
6040803b60 | |||
04ae812a11 | |||
a0ad12c45b | |||
9cc7fdaca9 | |||
545a79d406 | |||
7c34964f87 | |||
6a97ab9ecb | |||
ad6336231c | |||
2f36c9d66d | |||
ae966833a4 | |||
3b037acb01 | |||
8771fa5c17 | |||
bccc6aa8d1 | |||
da27be6de5 | |||
838c5c7770 | |||
02395413ba | |||
4c1682b365 | |||
e7613e4f8b | |||
685fd49dd7 | |||
6061f3d4fa | |||
d55025ecae | |||
e3e0e9796b | |||
ecb2a5f3c6 | |||
76ccebf099 | |||
78100aa963 | |||
0b56fc7598 | |||
c1199f1a8a | |||
c8c078e78a | |||
d36f9c2ac8 | |||
e1dd4ac9e7 | |||
e6a35199e0 | |||
3cd1c92162 | |||
be5ce6249f | |||
0321fe93cf | |||
2cbfac2c89 | |||
0a6705f43e | |||
97e14337e3 | |||
5e7d644efd | |||
40ae019e1d | |||
efbde4a179 | |||
f565edd76d | |||
a3b8c9c233 | |||
de92bc0234 | |||
e6377eac9b | |||
22726c1de8 | |||
bce3aeb218 | |||
81722610d5 | |||
5bfad04b41 | |||
880664c6ab | |||
8f0c61ed14 | |||
7d524668e0 | |||
75680c5866 | |||
a1d008e6f0 | |||
d5d62a4ffd | |||
b72bd5bb37 | |||
7fecd20a1d | |||
f586f22ce3 | |||
21848d6bf1 | |||
28c57c4144 | |||
4a6ebff473 | |||
5c8d955f61 | |||
baaf509637 | |||
db3e1b9ed5 | |||
1a1e3345f4 |
56
.github/workflows/build.yml
vendored
56
.github/workflows/build.yml
vendored
@ -16,6 +16,11 @@ on:
|
||||
- Alpha
|
||||
- Beta
|
||||
- Meta
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.ref }}-${{ github.workflow }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
REGISTRY: docker.io
|
||||
jobs:
|
||||
@ -46,26 +51,27 @@ jobs:
|
||||
target: "linux-mips-softfloat linux-mips-hardfloat linux-mipsle-softfloat linux-mipsle-hardfloat",
|
||||
id: "4",
|
||||
}
|
||||
- { type: "WithoutCGO", target: "linux-386 linux-riscv64", id: "5" }
|
||||
- {
|
||||
type: "WithoutCGO",
|
||||
target: "freebsd-386 freebsd-amd64 freebsd-arm64",
|
||||
id: "5",
|
||||
}
|
||||
- {
|
||||
type: "WithoutCGO",
|
||||
target: "windows-amd64-compatible windows-amd64 windows-386",
|
||||
id: "6",
|
||||
}
|
||||
- {
|
||||
type: "WithoutCGO",
|
||||
target: "windows-arm64 windows-arm32v7",
|
||||
target: "windows-amd64-compatible windows-amd64 windows-386",
|
||||
id: "7",
|
||||
}
|
||||
- {
|
||||
type: "WithoutCGO",
|
||||
target: "darwin-amd64 darwin-arm64 android-arm64",
|
||||
target: "windows-arm64 windows-arm32v7",
|
||||
id: "8",
|
||||
}
|
||||
- {
|
||||
type: "WithoutCGO",
|
||||
target: "darwin-amd64 darwin-arm64 android-arm64",
|
||||
id: "9",
|
||||
}
|
||||
- { type: "WithCGO", target: "windows/*", id: "1" }
|
||||
- { type: "WithCGO", target: "linux/386", id: "2" }
|
||||
- { type: "WithCGO", target: "linux/amd64", id: "3" }
|
||||
@ -108,10 +114,11 @@ jobs:
|
||||
|
||||
- name: Set ENV
|
||||
run: |
|
||||
sudo timedatectl set-timezone "Asia/Shanghai"
|
||||
echo "NAME=clash.meta" >> $GITHUB_ENV
|
||||
echo "REPO=${{ github.repository }}" >> $GITHUB_ENV
|
||||
echo "ShortSHA=$(git rev-parse --short ${{ github.sha }})" >> $GITHUB_ENV
|
||||
echo "BUILDTIME=$(date -u)" >> $GITHUB_ENV
|
||||
echo "BUILDTIME=$(date)" >> $GITHUB_ENV
|
||||
echo "BRANCH=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
@ -193,6 +200,10 @@ jobs:
|
||||
ls -la
|
||||
cd ..
|
||||
|
||||
- name: Save version
|
||||
run: echo ${VERSION} > bin/version.txt
|
||||
shell: bash
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ success() }}
|
||||
with:
|
||||
@ -202,7 +213,7 @@ jobs:
|
||||
Upload-Prerelease:
|
||||
permissions: write-all
|
||||
if: ${{ github.ref_type=='branch' }}
|
||||
needs: [ Build ]
|
||||
needs: [Build]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
@ -221,6 +232,11 @@ jobs:
|
||||
tag: Prerelease-${{ github.ref_name }}
|
||||
deleteOnlyFromDrafts: false
|
||||
|
||||
- name: Set Env
|
||||
run: |
|
||||
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- name: Tag Repo
|
||||
uses: richardsimko/update-tag@v1.0.6
|
||||
with:
|
||||
@ -228,20 +244,34 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- run: |
|
||||
cat > release.txt << 'EOF'
|
||||
Release created at ${{ env.BUILDTIME }}
|
||||
Synchronize ${{ github.ref_name }} branch code updates, keeping only the latest version
|
||||
<br>
|
||||
### release version
|
||||
`default(not specified in file name)`: compiled with GOAMD64=v3
|
||||
`cgo`: support lwip tun stack, compiled with GOAMD64=v1
|
||||
`compatible`: compiled with GOAMD64=v1
|
||||
Check details between different architectural levels [here](https://github.com/golang/go/wiki/MinimumRequirements#amd64).
|
||||
EOF
|
||||
|
||||
- name: Upload Prerelease
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: ${{ success() }}
|
||||
with:
|
||||
tag: ${{ github.ref_name }}
|
||||
tag_name: Prerelease-${{ github.ref_name }}
|
||||
files: bin/*
|
||||
files: |
|
||||
bin/*
|
||||
prerelease: true
|
||||
generate_release_notes: true
|
||||
body_path: release.txt
|
||||
|
||||
Upload-Release:
|
||||
permissions: write-all
|
||||
if: ${{ github.ref_type=='tag' }}
|
||||
needs: [ Build ]
|
||||
needs: [Build]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
@ -264,7 +294,7 @@ jobs:
|
||||
|
||||
Docker:
|
||||
permissions: write-all
|
||||
needs: [ Build ]
|
||||
needs: [Build]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
@ -321,5 +351,7 @@ jobs:
|
||||
linux/386
|
||||
linux/amd64
|
||||
linux/arm64/v8
|
||||
linux/arm/v7
|
||||
# linux/riscv64
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
@ -1,4 +1,6 @@
|
||||
FROM alpine:latest as builder
|
||||
ARG TARGETPLATFORM
|
||||
RUN echo "I'm building for $TARGETPLATFORM"
|
||||
|
||||
RUN apk add --no-cache gzip && \
|
||||
mkdir /clash-config && \
|
||||
@ -10,7 +12,7 @@ COPY docker/file-name.sh /clash/file-name.sh
|
||||
WORKDIR /clash
|
||||
COPY bin/ bin/
|
||||
RUN FILE_NAME=`sh file-name.sh` && echo $FILE_NAME && \
|
||||
FILE_NAME=`ls bin/ | egrep "$FILE_NAME.*"|awk NR==1` && \
|
||||
FILE_NAME=`ls bin/ | egrep "$FILE_NAME.*"|awk NR==1` && echo $FILE_NAME && \
|
||||
mv bin/$FILE_NAME clash.gz && gzip -d clash.gz && echo "$FILE_NAME" > /clash-config/test
|
||||
FROM alpine:latest
|
||||
LABEL org.opencontainers.image.source="https://github.com/MetaCubeX/Clash.Meta"
|
||||
|
3
Makefile
3
Makefile
@ -101,6 +101,9 @@ linux-mips64:
|
||||
linux-mips64le:
|
||||
GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-riscv64:
|
||||
GOARCH=riscv64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
android-arm64:
|
||||
GOARCH=arm64 GOOS=android $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
|
@ -34,7 +34,7 @@ func NewInner(conn net.Conn, dst string, host string) *context.ConnContext {
|
||||
metadata := &C.Metadata{}
|
||||
metadata.NetWork = C.TCP
|
||||
metadata.Type = C.INNER
|
||||
metadata.DNSMode = C.DNSMapping
|
||||
metadata.DNSMode = C.DNSNormal
|
||||
metadata.Host = host
|
||||
metadata.Process = C.ClashName
|
||||
if h, port, err := net.SplitHostPort(dst); err == nil {
|
||||
|
@ -8,10 +8,9 @@ import (
|
||||
"strings"
|
||||
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
type Base struct {
|
||||
@ -35,7 +34,7 @@ func (b *Base) Name() string {
|
||||
// Id implements C.ProxyAdapter
|
||||
func (b *Base) Id() string {
|
||||
if b.id == "" {
|
||||
id, err := uuid.NewV6()
|
||||
id, err := utils.UnsafeUUIDGenerator.NewV6()
|
||||
if err != nil {
|
||||
b.id = b.name
|
||||
} else {
|
||||
@ -140,10 +139,15 @@ func (b *Base) DialOptions(opts ...dialer.Option) []dialer.Option {
|
||||
default:
|
||||
}
|
||||
|
||||
if b.tfo {
|
||||
opts = append(opts, dialer.WithTFO(true))
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
type BasicOption struct {
|
||||
TFO bool `proxy:"tfo,omitempty" group:"tfo,omitempty"`
|
||||
Interface string `proxy:"interface-name,omitempty" group:"interface-name,omitempty"`
|
||||
RoutingMark int `proxy:"routing-mark,omitempty" group:"routing-mark,omitempty"`
|
||||
IPVersion string `proxy:"ip-version,omitempty" group:"ip-version,omitempty"`
|
||||
|
@ -170,6 +170,7 @@ func NewHttp(option HttpOption) (*Http, error) {
|
||||
name: option.Name,
|
||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||
tp: C.Http,
|
||||
tfo: option.TFO,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
|
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
|
||||
}
|
@ -6,6 +6,7 @@ import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/buf"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
@ -16,12 +17,12 @@ type Reject struct {
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||
return NewConn(&nopConn{}, r), nil
|
||||
return NewConn(nopConn{}, r), nil
|
||||
}
|
||||
|
||||
// ListenPacketContext implements C.ProxyAdapter
|
||||
func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||
return newPacketConn(&nopPacketConn{}, r), nil
|
||||
return newPacketConn(nopPacketConn{}, r), nil
|
||||
}
|
||||
|
||||
func NewReject() *Reject {
|
||||
@ -48,27 +49,37 @@ func NewPass() *Reject {
|
||||
|
||||
type nopConn struct{}
|
||||
|
||||
func (rw *nopConn) Read(b []byte) (int, error) {
|
||||
func (rw nopConn) Read(b []byte) (int, error) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
func (rw *nopConn) Write(b []byte) (int, error) {
|
||||
func (rw nopConn) ReadBuffer(buffer *buf.Buffer) error {
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
func (rw nopConn) Write(b []byte) (int, error) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
func (rw *nopConn) Close() error { return nil }
|
||||
func (rw *nopConn) LocalAddr() net.Addr { return nil }
|
||||
func (rw *nopConn) RemoteAddr() net.Addr { return nil }
|
||||
func (rw *nopConn) SetDeadline(time.Time) error { return nil }
|
||||
func (rw *nopConn) SetReadDeadline(time.Time) error { return nil }
|
||||
func (rw *nopConn) SetWriteDeadline(time.Time) error { return nil }
|
||||
func (rw nopConn) WriteBuffer(buffer *buf.Buffer) error {
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
func (rw nopConn) Close() error { return nil }
|
||||
func (rw nopConn) LocalAddr() net.Addr { return nil }
|
||||
func (rw nopConn) RemoteAddr() net.Addr { return nil }
|
||||
func (rw nopConn) SetDeadline(time.Time) error { return nil }
|
||||
func (rw nopConn) SetReadDeadline(time.Time) error { return nil }
|
||||
func (rw nopConn) SetWriteDeadline(time.Time) error { return nil }
|
||||
|
||||
var udpAddrIPv4Unspecified = &net.UDPAddr{IP: net.IPv4zero, Port: 0}
|
||||
|
||||
type nopPacketConn struct{}
|
||||
|
||||
func (npc *nopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { return len(b), nil }
|
||||
func (npc *nopPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { return 0, nil, io.EOF }
|
||||
func (npc *nopPacketConn) Close() error { return nil }
|
||||
func (npc *nopPacketConn) LocalAddr() net.Addr { return &net.UDPAddr{IP: net.IPv4zero, Port: 0} }
|
||||
func (npc *nopPacketConn) SetDeadline(time.Time) error { return nil }
|
||||
func (npc *nopPacketConn) SetReadDeadline(time.Time) error { return nil }
|
||||
func (npc *nopPacketConn) SetWriteDeadline(time.Time) error { return nil }
|
||||
func (npc nopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { return len(b), nil }
|
||||
func (npc nopPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { return 0, nil, io.EOF }
|
||||
func (npc nopPacketConn) Close() error { return nil }
|
||||
func (npc nopPacketConn) LocalAddr() net.Addr { return udpAddrIPv4Unspecified }
|
||||
func (npc nopPacketConn) SetDeadline(time.Time) error { return nil }
|
||||
func (npc nopPacketConn) SetReadDeadline(time.Time) error { return nil }
|
||||
func (npc nopPacketConn) SetWriteDeadline(time.Time) error { return nil }
|
||||
|
@ -2,21 +2,23 @@ package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/shadowtls"
|
||||
"github.com/Dreamacro/clash/transport/restls"
|
||||
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
|
||||
shadowtls "github.com/Dreamacro/clash/transport/sing-shadowtls"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
|
||||
|
||||
restlsC "github.com/3andne/restls-client-go"
|
||||
shadowsocks "github.com/metacubex/sing-shadowsocks"
|
||||
"github.com/metacubex/sing-shadowsocks/shadowimpl"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
@ -33,21 +35,22 @@ type ShadowSocks struct {
|
||||
obfsMode string
|
||||
obfsOption *simpleObfsOption
|
||||
v2rayOption *v2rayObfs.Option
|
||||
shadowTLSOption *shadowTLSOption
|
||||
tlsConfig *tls.Config
|
||||
shadowTLSOption *shadowtls.ShadowTLSOption
|
||||
restlsConfig *restlsC.Config
|
||||
}
|
||||
|
||||
type ShadowSocksOption struct {
|
||||
BasicOption
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
Password string `proxy:"password"`
|
||||
Cipher string `proxy:"cipher"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
Plugin string `proxy:"plugin,omitempty"`
|
||||
PluginOpts map[string]any `proxy:"plugin-opts,omitempty"`
|
||||
UDPOverTCP bool `proxy:"udp-over-tcp,omitempty"`
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
Password string `proxy:"password"`
|
||||
Cipher string `proxy:"cipher"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
Plugin string `proxy:"plugin,omitempty"`
|
||||
PluginOpts map[string]any `proxy:"plugin-opts,omitempty"`
|
||||
UDPOverTCP bool `proxy:"udp-over-tcp,omitempty"`
|
||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
||||
}
|
||||
|
||||
type simpleObfsOption struct {
|
||||
@ -71,10 +74,34 @@ type shadowTLSOption struct {
|
||||
Host string `obfs:"host"`
|
||||
Fingerprint string `obfs:"fingerprint,omitempty"`
|
||||
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
|
||||
Version int `obfs:"version,omitempty"`
|
||||
}
|
||||
|
||||
type restlsOption struct {
|
||||
Password string `obfs:"password"`
|
||||
Host string `obfs:"host"`
|
||||
VersionHint string `obfs:"version-hint"`
|
||||
RestlsScript string `obfs:"restls-script,omitempty"`
|
||||
}
|
||||
|
||||
// StreamConn implements C.ProxyAdapter
|
||||
func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
switch ss.obfsMode {
|
||||
case shadowtls.Mode:
|
||||
// fix tls handshake not timeout
|
||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
||||
defer cancel()
|
||||
var err error
|
||||
c, err = shadowtls.NewShadowTLS(ctx, c, ss.shadowTLSOption)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
return ss.streamConn(c, metadata)
|
||||
}
|
||||
|
||||
func (ss *ShadowSocks) streamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
switch ss.obfsMode {
|
||||
case "tls":
|
||||
c = obfs.NewTLSObfs(c, ss.obfsOption.Host)
|
||||
@ -87,13 +114,25 @@ func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||
}
|
||||
case shadowtls.Mode:
|
||||
c = shadowtls.NewShadowTLS(c, ss.shadowTLSOption.Password, ss.tlsConfig)
|
||||
case restls.Mode:
|
||||
var err error
|
||||
c, err = restls.NewRestls(c, ss.restlsConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s (restls) connect error: %w", ss.addr, err)
|
||||
}
|
||||
}
|
||||
if metadata.NetWork == C.UDP && ss.option.UDPOverTCP {
|
||||
return ss.method.DialConn(c, M.ParseSocksaddr(uot.UOTMagicAddress+":443"))
|
||||
if N.NeedHandshake(c) {
|
||||
return ss.method.DialEarlyConn(c, M.ParseSocksaddr(uot.UOTMagicAddress+":443")), nil
|
||||
} else {
|
||||
return ss.method.DialConn(c, M.ParseSocksaddr(uot.UOTMagicAddress+":443"))
|
||||
}
|
||||
}
|
||||
if N.NeedHandshake(c) {
|
||||
return ss.method.DialEarlyConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil
|
||||
} else {
|
||||
return ss.method.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
}
|
||||
return ss.method.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
@ -113,7 +152,15 @@ func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Diale
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
|
||||
c, err = ss.StreamConn(c, metadata)
|
||||
switch ss.obfsMode {
|
||||
case shadowtls.Mode:
|
||||
c, err = shadowtls.NewShadowTLS(ctx, c, ss.shadowTLSOption)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
c, err = ss.streamConn(c, metadata)
|
||||
return NewConn(c, ss), err
|
||||
}
|
||||
|
||||
@ -164,15 +211,15 @@ func (ss *ShadowSocks) SupportUOT() bool {
|
||||
|
||||
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
method, err := shadowimpl.FetchMethod(option.Cipher, option.Password)
|
||||
method, err := shadowimpl.FetchMethod(option.Cipher, option.Password, time.Now)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ss %s initialize error: %w", addr, err)
|
||||
}
|
||||
|
||||
var v2rayOption *v2rayObfs.Option
|
||||
var obfsOption *simpleObfsOption
|
||||
var shadowTLSOpt *shadowTLSOption
|
||||
var tlsConfig *tls.Config
|
||||
var shadowTLSOpt *shadowtls.ShadowTLSOption
|
||||
var restlsConfig *restlsC.Config
|
||||
obfsMode := ""
|
||||
|
||||
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
|
||||
@ -210,25 +257,34 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
}
|
||||
} else if option.Plugin == shadowtls.Mode {
|
||||
obfsMode = shadowtls.Mode
|
||||
shadowTLSOpt = &shadowTLSOption{}
|
||||
if err := decoder.Decode(option.PluginOpts, shadowTLSOpt); err != nil {
|
||||
opt := &shadowTLSOption{
|
||||
Version: 2,
|
||||
}
|
||||
if err := decoder.Decode(option.PluginOpts, opt); err != nil {
|
||||
return nil, fmt.Errorf("ss %s initialize shadow-tls-plugin error: %w", addr, err)
|
||||
}
|
||||
|
||||
tlsConfig = &tls.Config{
|
||||
NextProtos: shadowtls.DefaultALPN,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
InsecureSkipVerify: shadowTLSOpt.SkipCertVerify,
|
||||
ServerName: shadowTLSOpt.Host,
|
||||
shadowTLSOpt = &shadowtls.ShadowTLSOption{
|
||||
Password: opt.Password,
|
||||
Host: opt.Host,
|
||||
Fingerprint: opt.Fingerprint,
|
||||
ClientFingerprint: option.ClientFingerprint,
|
||||
SkipCertVerify: opt.SkipCertVerify,
|
||||
Version: opt.Version,
|
||||
}
|
||||
} else if option.Plugin == restls.Mode {
|
||||
obfsMode = restls.Mode
|
||||
restlsOpt := &restlsOption{}
|
||||
if err := decoder.Decode(option.PluginOpts, restlsOpt); err != nil {
|
||||
return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err)
|
||||
}
|
||||
|
||||
if len(shadowTLSOpt.Fingerprint) == 0 {
|
||||
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
|
||||
} else {
|
||||
if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, shadowTLSOpt.Fingerprint); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
restlsConfig, err = restlsC.NewRestlsConfig(restlsOpt.Host, restlsOpt.Password, restlsOpt.VersionHint, restlsOpt.RestlsScript, option.ClientFingerprint)
|
||||
restlsConfig.SessionTicketsDisabled = true
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return &ShadowSocks{
|
||||
@ -237,6 +293,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
addr: addr,
|
||||
tp: C.Shadowsocks,
|
||||
udp: option.UDP,
|
||||
tfo: option.TFO,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
@ -248,7 +305,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
v2rayOption: v2rayOption,
|
||||
obfsOption: obfsOption,
|
||||
shadowTLSOption: shadowTLSOpt,
|
||||
tlsConfig: tlsConfig,
|
||||
restlsConfig: restlsConfig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -163,6 +163,7 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
|
||||
addr: addr,
|
||||
tp: C.ShadowsocksR,
|
||||
udp: option.UDP,
|
||||
tfo: option.TFO,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
|
@ -167,6 +167,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
||||
addr: addr,
|
||||
tp: C.Snell,
|
||||
udp: option.UDP,
|
||||
tfo: option.TFO,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
|
@ -182,6 +182,7 @@ func NewSocks5(option Socks5Option) (*Socks5, error) {
|
||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||
tp: C.Socks5,
|
||||
udp: option.UDP,
|
||||
tfo: option.TFO,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
|
@ -26,25 +26,28 @@ type Trojan struct {
|
||||
gunTLSConfig *tls.Config
|
||||
gunConfig *gun.Config
|
||||
transport *gun.TransportWrap
|
||||
|
||||
realityConfig *tlsC.RealityConfig
|
||||
}
|
||||
|
||||
type TrojanOption struct {
|
||||
BasicOption
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
Password string `proxy:"password"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
Network string `proxy:"network,omitempty"`
|
||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
||||
Flow string `proxy:"flow,omitempty"`
|
||||
FlowShow bool `proxy:"flow-show,omitempty"`
|
||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
Password string `proxy:"password"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
Network string `proxy:"network,omitempty"`
|
||||
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
||||
Flow string `proxy:"flow,omitempty"`
|
||||
FlowShow bool `proxy:"flow-show,omitempty"`
|
||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
||||
}
|
||||
|
||||
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 {
|
||||
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig)
|
||||
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig, t.realityConfig)
|
||||
} else {
|
||||
c, err = t.plainStream(c)
|
||||
}
|
||||
@ -250,6 +253,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
addr: addr,
|
||||
tp: C.Trojan,
|
||||
udp: option.UDP,
|
||||
tfo: option.TFO,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
@ -258,6 +262,13 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
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" {
|
||||
dialFn := func(network, addr string) (net.Conn, error) {
|
||||
c, err := dialer.DialContext(context.Background(), "tcp", t.addr, t.Base.DialOptions()...)
|
||||
@ -284,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.gunConfig = &gun.Config{
|
||||
|
@ -51,6 +51,7 @@ type TuicOption struct {
|
||||
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
|
||||
ReceiveWindow int `proxy:"recv-window,omitempty"`
|
||||
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
@ -106,12 +107,14 @@ func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (pc net.Pack
|
||||
func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
serverName := option.Server
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
ServerName: serverName,
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
if option.SNI != "" {
|
||||
tlsConfig.ServerName = option.SNI
|
||||
}
|
||||
|
||||
var bs []byte
|
||||
var err error
|
||||
@ -213,6 +216,7 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
udp: true,
|
||||
tfo: option.FastOpen,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
},
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/transport/gun"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
"github.com/Dreamacro/clash/transport/vless"
|
||||
@ -41,6 +42,8 @@ type Vless struct {
|
||||
gunTLSConfig *tls.Config
|
||||
gunConfig *gun.Config
|
||||
transport *gun.TransportWrap
|
||||
|
||||
realityConfig *tlsC.RealityConfig
|
||||
}
|
||||
|
||||
type VlessOption struct {
|
||||
@ -57,6 +60,7 @@ type VlessOption struct {
|
||||
XUDP bool `proxy:"xudp,omitempty"`
|
||||
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
||||
Network string `proxy:"network,omitempty"`
|
||||
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||
HTTP2Opts HTTP2Options `proxy:"h2-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 {
|
||||
case "ws":
|
||||
|
||||
host, port, _ := net.SplitHostPort(v.addr)
|
||||
wsOpts := &vmess.WebsocketConfig{
|
||||
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)
|
||||
case "grpc":
|
||||
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig)
|
||||
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig)
|
||||
default:
|
||||
// default tcp network
|
||||
// handle TLS And XTLS
|
||||
@ -171,7 +174,7 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error) {
|
||||
host, _, _ := net.SplitHostPort(v.addr)
|
||||
|
||||
if v.isXTLSEnabled() && !isH2 {
|
||||
if v.isLegacyXTLSEnabled() && !isH2 {
|
||||
xtlsOpts := vless.XTLSConfig{
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
@ -190,6 +193,7 @@ func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error)
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
FingerPrint: v.option.Fingerprint,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
Reality: v.realityConfig,
|
||||
}
|
||||
|
||||
if isH2 {
|
||||
@ -206,8 +210,8 @@ func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error)
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (v *Vless) isXTLSEnabled() bool {
|
||||
return v.client.Addons != nil
|
||||
func (v *Vless) isLegacyXTLSEnabled() bool {
|
||||
return v.client.Addons != nil && v.client.Addons.Flow != vless.XRV
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
@ -479,6 +483,9 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
if option.Network != "ws" && len(option.Flow) >= 16 {
|
||||
option.Flow = option.Flow[:16]
|
||||
switch option.Flow {
|
||||
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{
|
||||
Flow: option.Flow,
|
||||
@ -510,6 +517,7 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
tp: C.Vless,
|
||||
udp: option.UDP,
|
||||
xudp: option.XUDP,
|
||||
tfo: option.TFO,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
@ -518,6 +526,11 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
option: &option,
|
||||
}
|
||||
|
||||
v.realityConfig, err = v.option.RealityOpts.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch option.Network {
|
||||
case "h2":
|
||||
if len(option.HTTP2Opts.Host) == 0 {
|
||||
@ -552,8 +565,7 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
v.gunTLSConfig = tlsConfig
|
||||
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
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
@ -34,32 +35,35 @@ type Vmess struct {
|
||||
gunTLSConfig *tls.Config
|
||||
gunConfig *gun.Config
|
||||
transport *gun.TransportWrap
|
||||
|
||||
realityConfig *tlsC.RealityConfig
|
||||
}
|
||||
|
||||
type VmessOption struct {
|
||||
BasicOption
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
UUID string `proxy:"uuid"`
|
||||
AlterID int `proxy:"alterId"`
|
||||
Cipher string `proxy:"cipher"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
Network string `proxy:"network,omitempty"`
|
||||
TLS bool `proxy:"tls,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
ServerName string `proxy:"servername,omitempty"`
|
||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
||||
PacketAddr bool `proxy:"packet-addr,omitempty"`
|
||||
XUDP bool `proxy:"xudp,omitempty"`
|
||||
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
||||
GlobalPadding bool `proxy:"global-padding,omitempty"`
|
||||
AuthenticatedLength bool `proxy:"authenticated-length,omitempty"`
|
||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
UUID string `proxy:"uuid"`
|
||||
AlterID int `proxy:"alterId"`
|
||||
Cipher string `proxy:"cipher"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
Network string `proxy:"network,omitempty"`
|
||||
TLS bool `proxy:"tls,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
ServerName string `proxy:"servername,omitempty"`
|
||||
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
||||
PacketAddr bool `proxy:"packet-addr,omitempty"`
|
||||
XUDP bool `proxy:"xudp,omitempty"`
|
||||
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
||||
GlobalPadding bool `proxy:"global-padding,omitempty"`
|
||||
AuthenticatedLength bool `proxy:"authenticated-length,omitempty"`
|
||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
||||
}
|
||||
|
||||
type HTTPOptions struct {
|
||||
@ -94,7 +98,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
|
||||
switch v.option.Network {
|
||||
case "ws":
|
||||
|
||||
host, port, _ := net.SplitHostPort(v.addr)
|
||||
wsOpts := &clashVMess.WebsocketConfig{
|
||||
Host: host,
|
||||
@ -143,12 +146,12 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
Reality: v.realityConfig,
|
||||
}
|
||||
|
||||
if v.option.ServerName != "" {
|
||||
tlsOpts.Host = v.option.ServerName
|
||||
}
|
||||
|
||||
c, err = clashVMess.StreamTLSConn(c, tlsOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -171,6 +174,7 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
NextProtos: []string{"h2"},
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
Reality: v.realityConfig,
|
||||
}
|
||||
|
||||
if v.option.ServerName != "" {
|
||||
@ -189,7 +193,7 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
|
||||
c, err = clashVMess.StreamH2Conn(c, h2Opts)
|
||||
case "grpc":
|
||||
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig)
|
||||
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig)
|
||||
default:
|
||||
// handle TLS
|
||||
if v.option.TLS {
|
||||
@ -198,6 +202,7 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
Reality: v.realityConfig,
|
||||
}
|
||||
|
||||
if v.option.ServerName != "" {
|
||||
@ -213,12 +218,24 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
}
|
||||
if metadata.NetWork == C.UDP {
|
||||
if v.option.XUDP {
|
||||
return v.client.DialXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
if N.NeedHandshake(c) {
|
||||
return v.client.DialEarlyXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil
|
||||
} else {
|
||||
return v.client.DialXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
}
|
||||
} else {
|
||||
return v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
if N.NeedHandshake(c) {
|
||||
return v.client.DialEarlyPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil
|
||||
} else {
|
||||
return v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return v.client.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
if N.NeedHandshake(c) {
|
||||
return v.client.DialEarlyConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil
|
||||
} else {
|
||||
return v.client.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -289,9 +306,17 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
||||
}(c)
|
||||
|
||||
if v.option.XUDP {
|
||||
c, err = v.client.DialXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
if N.NeedHandshake(c) {
|
||||
c = v.client.DialEarlyXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
} else {
|
||||
c, err = v.client.DialXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
}
|
||||
} else {
|
||||
c, err = v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
if N.NeedHandshake(c) {
|
||||
c = v.client.DialEarlyPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
} else {
|
||||
c, err = v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@ -387,6 +412,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
tp: C.Vmess,
|
||||
udp: option.UDP,
|
||||
xudp: option.XUDP,
|
||||
tfo: option.TFO,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
@ -429,9 +455,14 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
v.gunTLSConfig = tlsConfig
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@ import (
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/listener/sing"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
wireguard "github.com/metacubex/sing-wireguard"
|
||||
|
||||
@ -34,7 +34,7 @@ type WireGuard struct {
|
||||
bind *wireguard.ClientBind
|
||||
device *device.Device
|
||||
tunDevice wireguard.Device
|
||||
dialer *wgDialer
|
||||
dialer *wgSingDialer
|
||||
startOnce sync.Once
|
||||
startErr error
|
||||
}
|
||||
@ -56,16 +56,28 @@ type WireGuardOption struct {
|
||||
PersistentKeepalive int `proxy:"persistent-keepalive,omitempty"`
|
||||
}
|
||||
|
||||
type wgDialer struct {
|
||||
options []dialer.Option
|
||||
type wgSingDialer struct {
|
||||
dialer dialer.Dialer
|
||||
}
|
||||
|
||||
func (d *wgDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
return dialer.DialContext(ctx, network, destination.String(), d.options...)
|
||||
var _ N.Dialer = &wgSingDialer{}
|
||||
|
||||
func (d *wgSingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
return d.dialer.DialContext(ctx, network, destination.String())
|
||||
}
|
||||
|
||||
func (d *wgDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
return dialer.ListenPacket(ctx, dialer.ParseNetwork("udp", destination.Addr), "", d.options...)
|
||||
func (d *wgSingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
return d.dialer.ListenPacket(ctx, "udp", "", destination.AddrPort())
|
||||
}
|
||||
|
||||
type wgNetDialer struct {
|
||||
tunDevice wireguard.Device
|
||||
}
|
||||
|
||||
var _ dialer.NetDialer = &wgNetDialer{}
|
||||
|
||||
func (d wgNetDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
return d.tunDevice.DialContext(ctx, network, M.ParseSocksaddr(address).Unwrap())
|
||||
}
|
||||
|
||||
func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
||||
@ -79,7 +91,7 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
},
|
||||
dialer: &wgDialer{},
|
||||
dialer: &wgSingDialer{dialer: dialer.NewDialer()},
|
||||
}
|
||||
runtime.SetFinalizer(outbound, closeWireGuard)
|
||||
|
||||
@ -174,14 +186,14 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
||||
}
|
||||
outbound.device = device.NewDevice(outbound.tunDevice, outbound.bind, &device.Logger{
|
||||
Verbosef: func(format string, args ...interface{}) {
|
||||
sing.Logger.Debug(fmt.Sprintf(strings.ToLower(format), args...))
|
||||
log.SingLogger.Debug(fmt.Sprintf(strings.ToLower(format), args...))
|
||||
},
|
||||
Errorf: func(format string, args ...interface{}) {
|
||||
sing.Logger.Error(fmt.Sprintf(strings.ToLower(format), args...))
|
||||
log.SingLogger.Error(fmt.Sprintf(strings.ToLower(format), args...))
|
||||
},
|
||||
}, option.Workers)
|
||||
if debug.Enabled {
|
||||
sing.Logger.Trace("created wireguard ipc conf: \n", ipcConf)
|
||||
log.SingLogger.Trace("created wireguard ipc conf: \n", ipcConf)
|
||||
}
|
||||
err = outbound.device.IpcSet(ipcConf)
|
||||
if err != nil {
|
||||
@ -199,7 +211,8 @@ func closeWireGuard(w *WireGuard) {
|
||||
}
|
||||
|
||||
func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||
w.dialer.options = opts
|
||||
options := w.Base.DialOptions(opts...)
|
||||
w.dialer.dialer = dialer.NewDialer(options...)
|
||||
var conn net.Conn
|
||||
w.startOnce.Do(func() {
|
||||
w.startErr = w.tunDevice.Start()
|
||||
@ -208,15 +221,12 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts
|
||||
return nil, w.startErr
|
||||
}
|
||||
if !metadata.Resolved() {
|
||||
var addrs []netip.Addr
|
||||
addrs, err = resolver.LookupIP(ctx, metadata.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, err = N.DialSerial(ctx, w.tunDevice, "tcp", M.ParseSocksaddr(metadata.RemoteAddress()), addrs)
|
||||
options = append(options, dialer.WithResolver(resolver.DefaultResolver))
|
||||
options = append(options, dialer.WithNetDialer(wgNetDialer{tunDevice: w.tunDevice}))
|
||||
conn, err = dialer.NewDialer(options...).DialContext(ctx, "tcp", metadata.RemoteAddress())
|
||||
} else {
|
||||
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 {
|
||||
return nil, err
|
||||
@ -228,7 +238,8 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts
|
||||
}
|
||||
|
||||
func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||
w.dialer.options = opts
|
||||
options := w.Base.DialOptions(opts...)
|
||||
w.dialer.dialer = dialer.NewDialer(options...)
|
||||
var pc net.PacketConn
|
||||
w.startOnce.Do(func() {
|
||||
w.startErr = w.tunDevice.Start()
|
||||
@ -247,7 +258,7 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/common/callback"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
@ -30,11 +32,23 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata, opts .
|
||||
c, err := proxy.DialContext(ctx, metadata, f.Base.DialOptions(opts...)...)
|
||||
if err == nil {
|
||||
c.AppendToChains(f)
|
||||
f.onDialSuccess()
|
||||
} else {
|
||||
f.onDialFailed(proxy.Type(), err)
|
||||
}
|
||||
|
||||
if N.NeedHandshake(c) {
|
||||
c = &callback.FirstWriteCallBackConn{
|
||||
Conn: c,
|
||||
Callback: func(err error) {
|
||||
if err == nil {
|
||||
f.onDialSuccess()
|
||||
} else {
|
||||
f.onDialFailed(proxy.Type(), err)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return c, err
|
||||
}
|
||||
|
||||
|
@ -6,11 +6,14 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
"github.com/Dreamacro/clash/common/callback"
|
||||
"github.com/Dreamacro/clash/common/murmur3"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
@ -18,7 +21,7 @@ import (
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
type strategyFn = func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy
|
||||
type strategyFn = func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy
|
||||
|
||||
type LoadBalance struct {
|
||||
*GroupBase
|
||||
@ -83,17 +86,27 @@ func jumpHash(key uint64, buckets int32) int32 {
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) {
|
||||
proxy := lb.Unwrap(metadata, true)
|
||||
|
||||
defer func() {
|
||||
if err == nil {
|
||||
c.AppendToChains(lb)
|
||||
lb.onDialSuccess()
|
||||
} else {
|
||||
lb.onDialFailed(proxy.Type(), err)
|
||||
}
|
||||
}()
|
||||
|
||||
c, err = proxy.DialContext(ctx, metadata, lb.Base.DialOptions(opts...)...)
|
||||
|
||||
if err == nil {
|
||||
c.AppendToChains(lb)
|
||||
} else {
|
||||
lb.onDialFailed(proxy.Type(), err)
|
||||
}
|
||||
|
||||
if N.NeedHandshake(c) {
|
||||
c = &callback.FirstWriteCallBackConn{
|
||||
Conn: c,
|
||||
Callback: func(err error) {
|
||||
if err == nil {
|
||||
lb.onDialSuccess()
|
||||
} else {
|
||||
lb.onDialFailed(proxy.Type(), err)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@ -115,22 +128,26 @@ func (lb *LoadBalance) SupportUDP() bool {
|
||||
}
|
||||
|
||||
func strategyRoundRobin() strategyFn {
|
||||
flag := true
|
||||
idx := 0
|
||||
return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy {
|
||||
idxMutex := sync.Mutex{}
|
||||
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
|
||||
idxMutex.Lock()
|
||||
defer idxMutex.Unlock()
|
||||
|
||||
i := 0
|
||||
length := len(proxies)
|
||||
for i := 0; i < length; i++ {
|
||||
flag = !flag
|
||||
if flag {
|
||||
idx = (idx - 1) % length
|
||||
} else {
|
||||
idx = (idx + 2) % length
|
||||
}
|
||||
if idx < 0 {
|
||||
idx = idx + length
|
||||
}
|
||||
proxy := proxies[idx]
|
||||
|
||||
if touch {
|
||||
defer func() {
|
||||
idx = (idx + i) % length
|
||||
}()
|
||||
}
|
||||
|
||||
for ; i < length; i++ {
|
||||
id := (idx + i) % length
|
||||
proxy := proxies[id]
|
||||
if proxy.Alive() {
|
||||
i++
|
||||
return proxy
|
||||
}
|
||||
}
|
||||
@ -141,7 +158,7 @@ func strategyRoundRobin() strategyFn {
|
||||
|
||||
func strategyConsistentHashing() strategyFn {
|
||||
maxRetry := 5
|
||||
return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy {
|
||||
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
|
||||
key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
|
||||
buckets := int32(len(proxies))
|
||||
for i := 0; i < maxRetry; i, key = i+1, key+1 {
|
||||
@ -169,7 +186,7 @@ func strategyStickySessions() strategyFn {
|
||||
lruCache := cache.New[uint64, int](
|
||||
cache.WithAge[uint64, int](int64(ttl.Seconds())),
|
||||
cache.WithSize[uint64, int](1000))
|
||||
return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy {
|
||||
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
|
||||
key := uint64(murmur3.Sum32([]byte(getKeyWithSrcAndDst(metadata))))
|
||||
length := len(proxies)
|
||||
idx, has := lruCache.Get(key)
|
||||
@ -201,7 +218,7 @@ func strategyStickySessions() strategyFn {
|
||||
// Unwrap implements C.ProxyAdapter
|
||||
func (lb *LoadBalance) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
|
||||
proxies := lb.GetProxies(touch)
|
||||
return lb.strategyFn(proxies, metadata)
|
||||
return lb.strategyFn(proxies, metadata, touch)
|
||||
}
|
||||
|
||||
// MarshalJSON implements C.ProxyAdapter
|
||||
|
@ -176,7 +176,7 @@ func (r *Relay) proxies(metadata *C.Metadata, touch bool) ([]C.Proxy, []C.Proxy)
|
||||
}
|
||||
|
||||
func (r *Relay) Addr() string {
|
||||
proxies, _ := r.proxies(nil, true)
|
||||
proxies, _ := r.proxies(nil, false)
|
||||
return proxies[len(proxies)-1].Addr()
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/common/callback"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/singledo"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -38,10 +40,23 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ..
|
||||
c, err = proxy.DialContext(ctx, metadata, u.Base.DialOptions(opts...)...)
|
||||
if err == nil {
|
||||
c.AppendToChains(u)
|
||||
u.onDialSuccess()
|
||||
} else {
|
||||
u.onDialFailed(proxy.Type(), err)
|
||||
}
|
||||
|
||||
if N.NeedHandshake(c) {
|
||||
c = &callback.FirstWriteCallBackConn{
|
||||
Conn: c,
|
||||
Callback: func(err error) {
|
||||
if err == nil {
|
||||
u.onDialSuccess()
|
||||
} else {
|
||||
u.onDialFailed(proxy.Type(), err)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return c, err
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,11 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
||||
switch proxyType {
|
||||
case "ss":
|
||||
ssOption := &outbound.ShadowSocksOption{}
|
||||
|
||||
if GlobalUtlsClient := tlsC.GetGlobalFingerprint(); len(GlobalUtlsClient) != 0 {
|
||||
ssOption.ClientFingerprint = GlobalUtlsClient
|
||||
}
|
||||
|
||||
err = decoder.Decode(mapping, ssOption)
|
||||
if err != nil {
|
||||
break
|
||||
|
@ -6,10 +6,10 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/common/batch"
|
||||
"github.com/Dreamacro/clash/common/singledo"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
@ -77,7 +77,7 @@ func (hc *HealthCheck) touch() {
|
||||
func (hc *HealthCheck) check() {
|
||||
_, _, _ = hc.singleDo.Do(func() (struct{}, error) {
|
||||
id := ""
|
||||
if uid, err := uuid.NewV4(); err == nil {
|
||||
if uid, err := utils.UnsafeUUIDGenerator.NewV4(); err == nil {
|
||||
id = uid.String()
|
||||
}
|
||||
log.Debugln("Start New Health Checking {%s}", id)
|
||||
|
@ -5,9 +5,15 @@ import (
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
)
|
||||
|
||||
const BufferSize = buf.BufferSize
|
||||
|
||||
type Buffer = buf.Buffer
|
||||
|
||||
var New = buf.New
|
||||
var StackNew = buf.StackNew
|
||||
var StackNewSize = buf.StackNewSize
|
||||
var With = buf.With
|
||||
|
||||
var KeepAlive = common.KeepAlive
|
||||
|
||||
//go:norace
|
||||
|
25
common/callback/callback.go
Normal file
25
common/callback/callback.go
Normal file
@ -0,0 +1,25 @@
|
||||
package callback
|
||||
|
||||
import (
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
type FirstWriteCallBackConn struct {
|
||||
C.Conn
|
||||
Callback func(error)
|
||||
written bool
|
||||
}
|
||||
|
||||
func (c *FirstWriteCallBackConn) Write(b []byte) (n int, err error) {
|
||||
defer func() {
|
||||
if !c.written {
|
||||
c.written = true
|
||||
c.Callback(err)
|
||||
}
|
||||
}()
|
||||
return c.Conn.Write(b)
|
||||
}
|
||||
|
||||
func (c *FirstWriteCallBackConn) Upstream() any {
|
||||
return c.Conn
|
||||
}
|
@ -83,7 +83,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
trojan["port"] = urlTrojan.Port()
|
||||
trojan["password"] = urlTrojan.User.Username()
|
||||
trojan["udp"] = true
|
||||
trojan["skip-cert-verify"] = false
|
||||
trojan["skip-cert-verify"], _ = strconv.ParseBool(query.Get("allowInsecure"))
|
||||
|
||||
sni := query.Get("sni")
|
||||
if sni != "" {
|
||||
|
@ -2,12 +2,14 @@ package convert
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"github.com/metacubex/sing-shadowsocks/shadowimpl"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
|
||||
"github.com/metacubex/sing-shadowsocks/shadowimpl"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
var hostsSuffix = []string{
|
||||
@ -292,7 +294,7 @@ var (
|
||||
)
|
||||
|
||||
func RandHost() string {
|
||||
id, _ := uuid.NewV4()
|
||||
id, _ := utils.UnsafeUUIDGenerator.NewV4()
|
||||
base := strings.ToLower(base64.RawURLEncoding.EncodeToString(id.Bytes()))
|
||||
base = strings.ReplaceAll(base, "-", "")
|
||||
base = strings.ReplaceAll(base, "_", "")
|
||||
@ -301,11 +303,11 @@ func RandHost() string {
|
||||
prefix += string(buf[6:8]) + "-"
|
||||
prefix += string(buf[len(buf)-8:])
|
||||
|
||||
return prefix + hostsSuffix[rand.Intn(hostsLen)]
|
||||
return prefix + hostsSuffix[fastrand.Intn(hostsLen)]
|
||||
}
|
||||
|
||||
func RandUserAgent() string {
|
||||
return userAgents[rand.Intn(uaLen)]
|
||||
return userAgents[fastrand.Intn(uaLen)]
|
||||
}
|
||||
|
||||
func SetUserAgent(header http.Header) {
|
||||
@ -317,6 +319,6 @@ func SetUserAgent(header http.Header) {
|
||||
}
|
||||
|
||||
func VerifyMethod(cipher, password string) (err error) {
|
||||
_, err = shadowimpl.FetchMethod(cipher, password)
|
||||
_, err = shadowimpl.FetchMethod(cipher, password, time.Now)
|
||||
return
|
||||
}
|
||||
|
@ -12,13 +12,14 @@ var _ ExtendedConn = (*BufferedConn)(nil)
|
||||
type BufferedConn struct {
|
||||
r *bufio.Reader
|
||||
ExtendedConn
|
||||
peeked bool
|
||||
}
|
||||
|
||||
func NewBufferedConn(c net.Conn) *BufferedConn {
|
||||
if bc, ok := c.(*BufferedConn); ok {
|
||||
return bc
|
||||
}
|
||||
return &BufferedConn{bufio.NewReader(c), NewExtendedConn(c)}
|
||||
return &BufferedConn{bufio.NewReader(c), NewExtendedConn(c), false}
|
||||
}
|
||||
|
||||
// Reader returns the internal bufio.Reader.
|
||||
@ -26,11 +27,24 @@ func (c *BufferedConn) Reader() *bufio.Reader {
|
||||
return c.r
|
||||
}
|
||||
|
||||
func (c *BufferedConn) ResetPeeked() {
|
||||
c.peeked = false
|
||||
}
|
||||
|
||||
func (c *BufferedConn) Peeked() bool {
|
||||
return c.peeked
|
||||
}
|
||||
|
||||
// Peek returns the next n bytes without advancing the reader.
|
||||
func (c *BufferedConn) Peek(n int) ([]byte, error) {
|
||||
c.peeked = true
|
||||
return c.r.Peek(n)
|
||||
}
|
||||
|
||||
func (c *BufferedConn) Discard(n int) (discarded int, err error) {
|
||||
return c.r.Discard(n)
|
||||
}
|
||||
|
||||
func (c *BufferedConn) Read(p []byte) (int, error) {
|
||||
return c.r.Read(p)
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
"github.com/sagernet/sing/common/network"
|
||||
)
|
||||
@ -16,6 +17,13 @@ type ExtendedConn = network.ExtendedConn
|
||||
type ExtendedWriter = network.ExtendedWriter
|
||||
type ExtendedReader = network.ExtendedReader
|
||||
|
||||
func NeedHandshake(conn any) bool {
|
||||
if earlyConn, isEarlyConn := common.Cast[network.EarlyConn](conn); isEarlyConn && earlyConn.NeedHandshake() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Relay copies between left and right bidirectionally.
|
||||
func Relay(leftConn, rightConn net.Conn) {
|
||||
_ = bufio.CopyConn(context.TODO(), leftConn, rightConn)
|
||||
|
@ -1,10 +1,10 @@
|
||||
package pool
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
func TestAllocGet(t *testing.T) {
|
||||
@ -43,6 +43,6 @@ func TestAllocPutThenGet(t *testing.T) {
|
||||
|
||||
func BenchmarkMSB(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
msb(rand.Int())
|
||||
msb(fastrand.Int())
|
||||
}
|
||||
}
|
||||
|
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
|
||||
}
|
@ -2,15 +2,22 @@ package utils
|
||||
|
||||
import (
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
var uuidNamespace, _ = uuid.FromString("00000000-0000-0000-0000-000000000000")
|
||||
type fastRandReader struct{}
|
||||
|
||||
func (r fastRandReader) Read(p []byte) (int, error) {
|
||||
return fastrand.Read(p)
|
||||
}
|
||||
|
||||
var UnsafeUUIDGenerator = uuid.NewGenWithOptions(uuid.WithRandomReader(fastRandReader{}))
|
||||
|
||||
// UUIDMap https://github.com/XTLS/Xray-core/issues/158#issue-783294090
|
||||
func UUIDMap(str string) (uuid.UUID, error) {
|
||||
u, err := uuid.FromString(str)
|
||||
if err != nil {
|
||||
return uuid.NewV5(uuidNamespace, str), nil
|
||||
return UnsafeUUIDGenerator.NewV5(uuid.Nil, str), nil
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
@ -37,14 +37,25 @@ func bindControl(ifaceIdx int) controlFn {
|
||||
var innerErr error
|
||||
err = c.Control(func(fd uintptr) {
|
||||
handle := syscall.Handle(fd)
|
||||
bind6err := bind6(handle, ifaceIdx)
|
||||
bind4err := bind4(handle, ifaceIdx)
|
||||
switch network {
|
||||
case "tcp6", "udp6":
|
||||
innerErr = bind6(handle, ifaceIdx)
|
||||
_ = bind4(handle, ifaceIdx)
|
||||
default:
|
||||
innerErr = bind4(handle, ifaceIdx)
|
||||
// try bind ipv6, if failed, ignore. it's a workaround for windows disable interface ipv6
|
||||
_ = bind6(handle, ifaceIdx)
|
||||
case "ip6", "tcp6":
|
||||
innerErr = bind6err
|
||||
case "ip4", "tcp4", "udp4":
|
||||
innerErr = bind4err
|
||||
case "udp6":
|
||||
// golang will set network to udp6 when listenUDP on wildcard ip (eg: ":0", "")
|
||||
if (!addrPort.Addr().IsValid() || addrPort.Addr().IsUnspecified()) && bind6err != nil {
|
||||
// try bind ipv6, if failed, ignore. it's a workaround for windows disable interface ipv6
|
||||
if bind4err != nil {
|
||||
innerErr = bind6err
|
||||
} else {
|
||||
innerErr = bind4err
|
||||
}
|
||||
} else {
|
||||
innerErr = bind6err
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -2,26 +2,25 @@ package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
type dialFunc func(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error)
|
||||
|
||||
var (
|
||||
dialMux sync.Mutex
|
||||
actualSingleDialContext = singleDialContext
|
||||
actualDualStackDialContext = dualStackDialContext
|
||||
tcpConcurrent = false
|
||||
DisableIPv6 = false
|
||||
ErrorInvalidedNetworkStack = errors.New("invalided network stack")
|
||||
ErrorDisableIPv6 = errors.New("IPv6 is disabled, dialer cancel")
|
||||
dialMux sync.Mutex
|
||||
actualSingleStackDialContext = serialSingleStackDialContext
|
||||
actualDualStackDialContext = serialDualStackDialContext
|
||||
tcpConcurrent = false
|
||||
fallbackTimeout = 300 * time.Millisecond
|
||||
)
|
||||
|
||||
func applyOptions(options ...Option) *option {
|
||||
@ -54,29 +53,23 @@ func DialContext(ctx context.Context, network, address string, options ...Option
|
||||
network = fmt.Sprintf("%s%d", network, opt.network)
|
||||
}
|
||||
|
||||
ips, port, err := parseAddr(ctx, network, address, opt.resolver)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch network {
|
||||
case "tcp4", "tcp6", "udp4", "udp6":
|
||||
return actualSingleDialContext(ctx, network, address, opt)
|
||||
return actualSingleStackDialContext(ctx, network, ips, port, opt)
|
||||
case "tcp", "udp":
|
||||
return actualDualStackDialContext(ctx, network, address, opt)
|
||||
return actualDualStackDialContext(ctx, network, ips, port, opt)
|
||||
default:
|
||||
return nil, ErrorInvalidedNetworkStack
|
||||
}
|
||||
}
|
||||
|
||||
func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) {
|
||||
cfg := &option{
|
||||
interfaceName: DefaultInterface.Load(),
|
||||
routingMark: int(DefaultRoutingMark.Load()),
|
||||
}
|
||||
|
||||
for _, o := range DefaultOptions {
|
||||
o(cfg)
|
||||
}
|
||||
|
||||
for _, o := range options {
|
||||
o(cfg)
|
||||
}
|
||||
cfg := applyOptions(options...)
|
||||
|
||||
lc := &net.ListenConfig{}
|
||||
if cfg.interfaceName != "" {
|
||||
@ -96,26 +89,40 @@ func ListenPacket(ctx context.Context, network, address string, options ...Optio
|
||||
return lc.ListenPacket(ctx, network, address)
|
||||
}
|
||||
|
||||
func SetDial(concurrent bool) {
|
||||
func SetTcpConcurrent(concurrent bool) {
|
||||
dialMux.Lock()
|
||||
defer dialMux.Unlock()
|
||||
tcpConcurrent = concurrent
|
||||
if concurrent {
|
||||
actualSingleDialContext = concurrentSingleDialContext
|
||||
actualSingleStackDialContext = concurrentSingleStackDialContext
|
||||
actualDualStackDialContext = concurrentDualStackDialContext
|
||||
} else {
|
||||
actualSingleDialContext = singleDialContext
|
||||
actualDualStackDialContext = dualStackDialContext
|
||||
actualSingleStackDialContext = serialSingleStackDialContext
|
||||
actualDualStackDialContext = serialDualStackDialContext
|
||||
}
|
||||
|
||||
dialMux.Unlock()
|
||||
}
|
||||
|
||||
func GetDial() bool {
|
||||
func GetTcpConcurrent() bool {
|
||||
dialMux.Lock()
|
||||
defer dialMux.Unlock()
|
||||
return tcpConcurrent
|
||||
}
|
||||
|
||||
func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) {
|
||||
dialer := &net.Dialer{}
|
||||
address := net.JoinHostPort(destination.String(), port)
|
||||
|
||||
netDialer := opt.netDialer
|
||||
switch netDialer.(type) {
|
||||
case nil:
|
||||
netDialer = &net.Dialer{}
|
||||
case *net.Dialer:
|
||||
_netDialer := *netDialer.(*net.Dialer)
|
||||
netDialer = &_netDialer // make a copy
|
||||
default:
|
||||
return netDialer.DialContext(ctx, network, address)
|
||||
}
|
||||
|
||||
dialer := netDialer.(*net.Dialer)
|
||||
if opt.interfaceName != "" {
|
||||
if err := bindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil {
|
||||
return nil, err
|
||||
@ -124,328 +131,213 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
|
||||
if opt.routingMark != 0 {
|
||||
bindMarkToDialer(opt.routingMark, dialer, network, destination)
|
||||
}
|
||||
|
||||
if DisableIPv6 && destination.Is6() {
|
||||
return nil, ErrorDisableIPv6
|
||||
if opt.tfo {
|
||||
return dialTFO(ctx, *dialer, network, address)
|
||||
}
|
||||
|
||||
return dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port))
|
||||
return dialer.DialContext(ctx, network, address)
|
||||
}
|
||||
|
||||
func dualStackDialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func serialSingleStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
||||
return serialDialContext(ctx, network, ips, port, opt)
|
||||
}
|
||||
|
||||
func serialDualStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
||||
return dualStackDialContext(ctx, serialDialContext, network, ips, port, opt)
|
||||
}
|
||||
|
||||
func concurrentSingleStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
||||
return parallelDialContext(ctx, network, ips, port, opt)
|
||||
}
|
||||
|
||||
func concurrentDualStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
||||
if opt.prefer != 4 && opt.prefer != 6 {
|
||||
return parallelDialContext(ctx, network, ips, port, opt)
|
||||
}
|
||||
return dualStackDialContext(ctx, parallelDialContext, network, ips, port, opt)
|
||||
}
|
||||
|
||||
func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
||||
ipv4s, ipv6s := sortationAddr(ips)
|
||||
preferIPVersion := opt.prefer
|
||||
|
||||
fallbackTicker := time.NewTicker(fallbackTimeout)
|
||||
defer fallbackTicker.Stop()
|
||||
results := make(chan dialResult)
|
||||
returned := make(chan struct{})
|
||||
defer close(returned)
|
||||
|
||||
type dialResult struct {
|
||||
net.Conn
|
||||
error
|
||||
resolved bool
|
||||
ipv6 bool
|
||||
done bool
|
||||
}
|
||||
results := make(chan dialResult)
|
||||
var primary, fallback dialResult
|
||||
|
||||
startRacer := func(ctx context.Context, network, host string, r resolver.Resolver, ipv6 bool) {
|
||||
result := dialResult{ipv6: ipv6, done: true}
|
||||
racer := func(ips []netip.Addr, isPrimary bool) {
|
||||
result := dialResult{isPrimary: isPrimary}
|
||||
defer func() {
|
||||
select {
|
||||
case results <- result:
|
||||
case <-returned:
|
||||
if result.Conn != nil {
|
||||
if result.Conn != nil && result.error == nil {
|
||||
_ = result.Conn.Close()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var ip netip.Addr
|
||||
if ipv6 {
|
||||
if r == nil {
|
||||
ip, result.error = resolver.ResolveIPv6ProxyServerHost(ctx, host)
|
||||
} else {
|
||||
ip, result.error = resolver.ResolveIPv6WithResolver(ctx, host, r)
|
||||
}
|
||||
} else {
|
||||
if r == nil {
|
||||
ip, result.error = resolver.ResolveIPv4ProxyServerHost(ctx, host)
|
||||
} else {
|
||||
ip, result.error = resolver.ResolveIPv4WithResolver(ctx, host, r)
|
||||
}
|
||||
}
|
||||
if result.error != nil {
|
||||
return
|
||||
}
|
||||
result.resolved = true
|
||||
|
||||
result.Conn, result.error = dialContext(ctx, network, ip, port, opt)
|
||||
result.Conn, result.error = dialFn(ctx, network, ips, port, opt)
|
||||
}
|
||||
|
||||
go startRacer(ctx, network+"4", host, opt.resolver, false)
|
||||
go startRacer(ctx, network+"6", host, opt.resolver, true)
|
||||
|
||||
count := 2
|
||||
for i := 0; i < count; i++ {
|
||||
go racer(ipv4s, preferIPVersion != 6)
|
||||
go racer(ipv6s, preferIPVersion != 4)
|
||||
var fallback dialResult
|
||||
var errs []error
|
||||
for i := 0; i < 2; {
|
||||
select {
|
||||
case <-fallbackTicker.C:
|
||||
if fallback.error == nil && fallback.Conn != nil {
|
||||
return fallback.Conn, nil
|
||||
}
|
||||
case res := <-results:
|
||||
i++
|
||||
if res.error == nil {
|
||||
return res.Conn, nil
|
||||
}
|
||||
|
||||
if !res.ipv6 {
|
||||
primary = res
|
||||
} else {
|
||||
if res.isPrimary {
|
||||
return res.Conn, nil
|
||||
}
|
||||
fallback = res
|
||||
}
|
||||
|
||||
if primary.done && fallback.done {
|
||||
if primary.resolved {
|
||||
return nil, primary.error
|
||||
} else if fallback.resolved {
|
||||
return nil, fallback.error
|
||||
} else {
|
||||
if res.isPrimary {
|
||||
errs = append([]error{fmt.Errorf("connect failed: %w", res.error)}, errs...)
|
||||
} else {
|
||||
return nil, primary.error
|
||||
errs = append(errs, fmt.Errorf("connect failed: %w", res.error))
|
||||
}
|
||||
}
|
||||
case <-ctx.Done():
|
||||
err = ctx.Err()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
err = fmt.Errorf("dual stack dial failed")
|
||||
} else {
|
||||
err = fmt.Errorf("dual stack dial failed:%w", err)
|
||||
if fallback.error == nil && fallback.Conn != nil {
|
||||
return fallback.Conn, nil
|
||||
}
|
||||
return nil, err
|
||||
return nil, errorsJoin(errs...)
|
||||
}
|
||||
|
||||
func concurrentDualStackDialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func parallelDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
||||
if len(ips) == 0 {
|
||||
return nil, ErrorNoIpAddress
|
||||
}
|
||||
|
||||
var ips []netip.Addr
|
||||
if opt.resolver != nil {
|
||||
ips, err = resolver.LookupIPWithResolver(ctx, host, opt.resolver)
|
||||
} else {
|
||||
ips, err = resolver.LookupIPProxyServerHost(ctx, host)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return concurrentDialContext(ctx, network, ips, port, opt)
|
||||
}
|
||||
|
||||
func concurrentDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
||||
results := make(chan dialResult)
|
||||
returned := make(chan struct{})
|
||||
defer close(returned)
|
||||
|
||||
type dialResult struct {
|
||||
ip netip.Addr
|
||||
net.Conn
|
||||
error
|
||||
isPrimary bool
|
||||
done bool
|
||||
}
|
||||
|
||||
preferCount := atomic.NewInt32(0)
|
||||
results := make(chan dialResult)
|
||||
tcpRacer := func(ctx context.Context, ip netip.Addr) {
|
||||
result := dialResult{ip: ip, done: true}
|
||||
|
||||
racer := func(ctx context.Context, ip netip.Addr) {
|
||||
result := dialResult{isPrimary: true, ip: ip}
|
||||
defer func() {
|
||||
select {
|
||||
case results <- result:
|
||||
case <-returned:
|
||||
if result.Conn != nil {
|
||||
if result.Conn != nil && result.error == nil {
|
||||
_ = result.Conn.Close()
|
||||
}
|
||||
}
|
||||
}()
|
||||
if strings.Contains(network, "tcp") {
|
||||
network = "tcp"
|
||||
} else {
|
||||
network = "udp"
|
||||
}
|
||||
|
||||
if ip.Is6() {
|
||||
network += "6"
|
||||
if opt.prefer != 4 {
|
||||
result.isPrimary = true
|
||||
}
|
||||
}
|
||||
|
||||
if ip.Is4() {
|
||||
network += "4"
|
||||
if opt.prefer != 6 {
|
||||
result.isPrimary = true
|
||||
}
|
||||
}
|
||||
|
||||
if result.isPrimary {
|
||||
preferCount.Add(1)
|
||||
}
|
||||
|
||||
result.Conn, result.error = dialContext(ctx, network, ip, port, opt)
|
||||
}
|
||||
|
||||
for _, ip := range ips {
|
||||
go tcpRacer(ctx, ip)
|
||||
go racer(ctx, ip)
|
||||
}
|
||||
|
||||
connCount := len(ips)
|
||||
var fallback dialResult
|
||||
var primaryError error
|
||||
var finalError error
|
||||
for i := 0; i < connCount; i++ {
|
||||
select {
|
||||
case res := <-results:
|
||||
if res.error == nil {
|
||||
if res.isPrimary {
|
||||
return res.Conn, nil
|
||||
} else {
|
||||
if !fallback.done || fallback.error != nil {
|
||||
fallback = res
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if res.isPrimary {
|
||||
primaryError = res.error
|
||||
preferCount.Add(-1)
|
||||
if preferCount.Load() == 0 && fallback.done && fallback.error == nil {
|
||||
return fallback.Conn, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
case <-ctx.Done():
|
||||
if fallback.done && fallback.error == nil {
|
||||
return fallback.Conn, nil
|
||||
}
|
||||
finalError = ctx.Err()
|
||||
break
|
||||
var errs []error
|
||||
for i := 0; i < len(ips); i++ {
|
||||
res := <-results
|
||||
if res.error == nil {
|
||||
return res.Conn, nil
|
||||
}
|
||||
errs = append(errs, res.error)
|
||||
}
|
||||
|
||||
if fallback.done && fallback.error == nil {
|
||||
return fallback.Conn, nil
|
||||
if len(errs) > 0 {
|
||||
return nil, errorsJoin(errs...)
|
||||
}
|
||||
|
||||
if primaryError != nil {
|
||||
return nil, primaryError
|
||||
}
|
||||
|
||||
if fallback.error != nil {
|
||||
return nil, fallback.error
|
||||
}
|
||||
|
||||
if finalError == nil {
|
||||
finalError = fmt.Errorf("all ips %v tcp shake hands failed", ips)
|
||||
} else {
|
||||
finalError = fmt.Errorf("concurrent dial failed:%w", finalError)
|
||||
}
|
||||
|
||||
return nil, finalError
|
||||
return nil, os.ErrDeadlineExceeded
|
||||
}
|
||||
|
||||
func singleDialContext(ctx context.Context, network string, address string, opt *option) (net.Conn, error) {
|
||||
func serialDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
||||
if len(ips) == 0 {
|
||||
return nil, ErrorNoIpAddress
|
||||
}
|
||||
var errs []error
|
||||
for _, ip := range ips {
|
||||
if conn, err := dialContext(ctx, network, ip, port, opt); err == nil {
|
||||
return conn, nil
|
||||
} else {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return nil, errorsJoin(errs...)
|
||||
}
|
||||
|
||||
type dialResult struct {
|
||||
ip netip.Addr
|
||||
net.Conn
|
||||
error
|
||||
isPrimary bool
|
||||
}
|
||||
|
||||
func parseAddr(ctx context.Context, network, address string, preferResolver resolver.Resolver) ([]netip.Addr, string, error) {
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ip netip.Addr
|
||||
switch network {
|
||||
case "tcp4", "udp4":
|
||||
if opt.resolver == nil {
|
||||
ip, err = resolver.ResolveIPv4ProxyServerHost(ctx, host)
|
||||
} else {
|
||||
ip, err = resolver.ResolveIPv4WithResolver(ctx, host, opt.resolver)
|
||||
}
|
||||
default:
|
||||
if opt.resolver == nil {
|
||||
ip, err = resolver.ResolveIPv6ProxyServerHost(ctx, host)
|
||||
} else {
|
||||
ip, err = resolver.ResolveIPv6WithResolver(ctx, host, opt.resolver)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dialContext(ctx, network, ip, port, opt)
|
||||
}
|
||||
|
||||
func concurrentSingleDialContext(ctx context.Context, network string, address string, opt *option) (net.Conn, error) {
|
||||
switch network {
|
||||
case "tcp4", "udp4":
|
||||
return concurrentIPv4DialContext(ctx, network, address, opt)
|
||||
default:
|
||||
return concurrentIPv6DialContext(ctx, network, address, opt)
|
||||
}
|
||||
}
|
||||
|
||||
func concurrentIPv4DialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "-1", err
|
||||
}
|
||||
|
||||
var ips []netip.Addr
|
||||
if opt.resolver == nil {
|
||||
ips, err = resolver.LookupIPv4ProxyServerHost(ctx, host)
|
||||
} else {
|
||||
ips, err = resolver.LookupIPv4WithResolver(ctx, host, opt.resolver)
|
||||
switch network {
|
||||
case "tcp4", "udp4":
|
||||
if preferResolver == nil {
|
||||
ips, err = resolver.LookupIPv4ProxyServerHost(ctx, host)
|
||||
} else {
|
||||
ips, err = resolver.LookupIPv4WithResolver(ctx, host, preferResolver)
|
||||
}
|
||||
case "tcp6", "udp6":
|
||||
if preferResolver == nil {
|
||||
ips, err = resolver.LookupIPv6ProxyServerHost(ctx, host)
|
||||
} else {
|
||||
ips, err = resolver.LookupIPv6WithResolver(ctx, host, preferResolver)
|
||||
}
|
||||
default:
|
||||
if preferResolver == nil {
|
||||
ips, err = resolver.LookupIPProxyServerHost(ctx, host)
|
||||
} else {
|
||||
ips, err = resolver.LookupIPWithResolver(ctx, host, preferResolver)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "-1", fmt.Errorf("dns resolve failed: %w", err)
|
||||
}
|
||||
|
||||
return concurrentDialContext(ctx, network, ips, port, opt)
|
||||
for i, ip := range ips {
|
||||
if ip.Is4In6() {
|
||||
ips[i] = ip.Unmap()
|
||||
}
|
||||
}
|
||||
return ips, port, nil
|
||||
}
|
||||
|
||||
func concurrentIPv6DialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func sortationAddr(ips []netip.Addr) (ipv4s, ipv6s []netip.Addr) {
|
||||
for _, v := range ips {
|
||||
if v.Is4() { // 4in6 parse was in parseAddr
|
||||
ipv4s = append(ipv4s, v)
|
||||
} else {
|
||||
ipv6s = append(ipv6s, v)
|
||||
}
|
||||
}
|
||||
|
||||
var ips []netip.Addr
|
||||
if opt.resolver == nil {
|
||||
ips, err = resolver.LookupIPv6ProxyServerHost(ctx, host)
|
||||
} else {
|
||||
ips, err = resolver.LookupIPv6WithResolver(ctx, host, opt.resolver)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return concurrentDialContext(ctx, network, ips, port, opt)
|
||||
return
|
||||
}
|
||||
|
||||
type Dialer struct {
|
||||
Opt option
|
||||
opt option
|
||||
}
|
||||
|
||||
func (d Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
return DialContext(ctx, network, address, WithOption(d.Opt))
|
||||
return DialContext(ctx, network, address, WithOption(d.opt))
|
||||
}
|
||||
|
||||
func (d Dialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) {
|
||||
return ListenPacket(ctx, ParseNetwork(network, rAddrPort.Addr()), address, WithOption(d.Opt))
|
||||
opt := WithOption(d.opt)
|
||||
if rAddrPort.Addr().Unmap().IsLoopback() {
|
||||
// avoid "The requested address is not valid in its context."
|
||||
opt = WithInterface("")
|
||||
}
|
||||
return ListenPacket(ctx, ParseNetwork(network, rAddrPort.Addr()), address, opt)
|
||||
}
|
||||
|
||||
func NewDialer(options ...Option) Dialer {
|
||||
opt := applyOptions(options...)
|
||||
return Dialer{Opt: *opt}
|
||||
return Dialer{opt: *opt}
|
||||
}
|
||||
|
18
component/dialer/error.go
Normal file
18
component/dialer/error.go
Normal file
@ -0,0 +1,18 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrorNoIpAddress = errors.New("no ip address")
|
||||
ErrorInvalidedNetworkStack = errors.New("invalided network stack")
|
||||
)
|
||||
|
||||
func errorsJoin(errs ...error) error {
|
||||
// compatibility with golang<1.20
|
||||
// maybe use errors.Join(errs...) is better after we drop the old version's support
|
||||
return E.Errors(errs...)
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
|
||||
"go.uber.org/atomic"
|
||||
@ -12,13 +15,19 @@ var (
|
||||
DefaultRoutingMark = atomic.NewInt32(0)
|
||||
)
|
||||
|
||||
type NetDialer interface {
|
||||
DialContext(ctx context.Context, network, address string) (net.Conn, error)
|
||||
}
|
||||
|
||||
type option struct {
|
||||
interfaceName string
|
||||
addrReuse bool
|
||||
routingMark int
|
||||
network int
|
||||
prefer int
|
||||
tfo bool
|
||||
resolver resolver.Resolver
|
||||
netDialer NetDialer
|
||||
}
|
||||
|
||||
type Option func(opt *option)
|
||||
@ -69,6 +78,18 @@ func WithOnlySingleStack(isIPv4 bool) Option {
|
||||
}
|
||||
}
|
||||
|
||||
func WithTFO(tfo bool) Option {
|
||||
return func(opt *option) {
|
||||
opt.tfo = tfo
|
||||
}
|
||||
}
|
||||
|
||||
func WithNetDialer(netDialer NetDialer) Option {
|
||||
return func(opt *option) {
|
||||
opt.netDialer = netDialer
|
||||
}
|
||||
}
|
||||
|
||||
func WithOption(o option) Option {
|
||||
return func(opt *option) {
|
||||
*opt = o
|
||||
|
123
component/dialer/tfo.go
Normal file
123
component/dialer/tfo.go
Normal file
@ -0,0 +1,123 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/sagernet/tfo-go"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type tfoConn struct {
|
||||
net.Conn
|
||||
closed bool
|
||||
dialed chan bool
|
||||
cancel context.CancelFunc
|
||||
ctx context.Context
|
||||
dialFn func(ctx context.Context, earlyData []byte) (net.Conn, error)
|
||||
}
|
||||
|
||||
func (c *tfoConn) Dial(earlyData []byte) (err error) {
|
||||
c.Conn, err = c.dialFn(c.ctx, earlyData)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.dialed <- true
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *tfoConn) Read(b []byte) (n int, err error) {
|
||||
if c.closed {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
if c.Conn == nil {
|
||||
select {
|
||||
case <-c.ctx.Done():
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
case <-c.dialed:
|
||||
}
|
||||
}
|
||||
return c.Conn.Read(b)
|
||||
}
|
||||
|
||||
func (c *tfoConn) Write(b []byte) (n int, err error) {
|
||||
if c.closed {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
if c.Conn == nil {
|
||||
if err := c.Dial(b); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
return c.Conn.Write(b)
|
||||
}
|
||||
|
||||
func (c *tfoConn) Close() error {
|
||||
c.closed = true
|
||||
c.cancel()
|
||||
if c.Conn == nil {
|
||||
return nil
|
||||
}
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
func (c *tfoConn) LocalAddr() net.Addr {
|
||||
if c.Conn == nil {
|
||||
return nil
|
||||
}
|
||||
return c.Conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *tfoConn) RemoteAddr() net.Addr {
|
||||
if c.Conn == nil {
|
||||
return nil
|
||||
}
|
||||
return c.Conn.RemoteAddr()
|
||||
}
|
||||
|
||||
func (c *tfoConn) SetDeadline(t time.Time) error {
|
||||
if err := c.SetReadDeadline(t); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func (c *tfoConn) SetReadDeadline(t time.Time) error {
|
||||
if c.Conn == nil {
|
||||
return nil
|
||||
}
|
||||
return c.Conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (c *tfoConn) SetWriteDeadline(t time.Time) error {
|
||||
if c.Conn == nil {
|
||||
return nil
|
||||
}
|
||||
return c.Conn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func (c *tfoConn) Upstream() any {
|
||||
if c.Conn == nil { // ensure return a nil interface not an interface with nil value
|
||||
return nil
|
||||
}
|
||||
return c.Conn
|
||||
}
|
||||
|
||||
func (c *tfoConn) NeedHandshake() bool {
|
||||
return c.Conn == nil
|
||||
}
|
||||
|
||||
func dialTFO(ctx context.Context, netDialer net.Dialer, network, address string) (net.Conn, error) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
dialer := tfo.Dialer{Dialer: netDialer, DisableTFO: false}
|
||||
return &tfoConn{
|
||||
dialed: make(chan bool, 1),
|
||||
cancel: cancel,
|
||||
ctx: ctx,
|
||||
dialFn: func(ctx context.Context, earlyData []byte) (net.Conn, error) {
|
||||
return dialer.DialContext(ctx, network, address, earlyData)
|
||||
},
|
||||
}, nil
|
||||
}
|
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
|
||||
}
|
@ -4,15 +4,16 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -27,7 +28,7 @@ var (
|
||||
DisableIPv6 = true
|
||||
|
||||
// DefaultHosts aim to resolve hosts
|
||||
DefaultHosts = trie.New[netip.Addr]()
|
||||
DefaultHosts = NewHosts(trie.New[HostValue]())
|
||||
|
||||
// DefaultDNSTimeout defined the default dns request timeout
|
||||
DefaultDNSTimeout = time.Second * 5
|
||||
@ -51,9 +52,11 @@ type Resolver interface {
|
||||
|
||||
// LookupIPv4WithResolver same as LookupIPv4, but with a resolver
|
||||
func LookupIPv4WithResolver(ctx context.Context, host string, r Resolver) ([]netip.Addr, error) {
|
||||
if node := DefaultHosts.Search(host); node != nil {
|
||||
if ip := node.Data(); ip.Is4() {
|
||||
return []netip.Addr{node.Data()}, nil
|
||||
if node, ok := DefaultHosts.Search(host, false); ok {
|
||||
if addrs := utils.Filter(node.IPs, func(ip netip.Addr) bool {
|
||||
return ip.Is4()
|
||||
}); len(addrs) > 0 {
|
||||
return addrs, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,10 +72,6 @@ func LookupIPv4WithResolver(ctx context.Context, host string, r Resolver) ([]net
|
||||
return r.LookupIPv4(ctx, host)
|
||||
}
|
||||
|
||||
if DefaultResolver != nil {
|
||||
return DefaultResolver.LookupIPv4(ctx, host)
|
||||
}
|
||||
|
||||
ipAddrs, err := net.DefaultResolver.LookupNetIP(ctx, "ip4", host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -96,7 +95,7 @@ func ResolveIPv4WithResolver(ctx context.Context, host string, r Resolver) (neti
|
||||
} else if len(ips) == 0 {
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", ErrIPNotFound, host)
|
||||
}
|
||||
return ips[rand.Intn(len(ips))], nil
|
||||
return ips[fastrand.Intn(len(ips))], nil
|
||||
}
|
||||
|
||||
// ResolveIPv4 with a host, return ipv4
|
||||
@ -110,9 +109,11 @@ func LookupIPv6WithResolver(ctx context.Context, host string, r Resolver) ([]net
|
||||
return nil, ErrIPv6Disabled
|
||||
}
|
||||
|
||||
if node := DefaultHosts.Search(host); node != nil {
|
||||
if ip := node.Data(); ip.Is6() {
|
||||
return []netip.Addr{ip}, nil
|
||||
if node, ok := DefaultHosts.Search(host, false); ok {
|
||||
if addrs := utils.Filter(node.IPs, func(ip netip.Addr) bool {
|
||||
return ip.Is6()
|
||||
}); len(addrs) > 0 {
|
||||
return addrs, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,9 +127,6 @@ func LookupIPv6WithResolver(ctx context.Context, host string, r Resolver) ([]net
|
||||
if r != nil {
|
||||
return r.LookupIPv6(ctx, host)
|
||||
}
|
||||
if DefaultResolver != nil {
|
||||
return DefaultResolver.LookupIPv6(ctx, host)
|
||||
}
|
||||
|
||||
ipAddrs, err := net.DefaultResolver.LookupNetIP(ctx, "ip6", host)
|
||||
if err != nil {
|
||||
@ -153,7 +151,7 @@ func ResolveIPv6WithResolver(ctx context.Context, host string, r Resolver) (neti
|
||||
} else if len(ips) == 0 {
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", ErrIPNotFound, host)
|
||||
}
|
||||
return ips[rand.Intn(len(ips))], nil
|
||||
return ips[fastrand.Intn(len(ips))], nil
|
||||
}
|
||||
|
||||
func ResolveIPv6(ctx context.Context, host string) (netip.Addr, error) {
|
||||
@ -162,8 +160,8 @@ func ResolveIPv6(ctx context.Context, host string) (netip.Addr, error) {
|
||||
|
||||
// LookupIPWithResolver same as LookupIP, but with a resolver
|
||||
func LookupIPWithResolver(ctx context.Context, host string, r Resolver) ([]netip.Addr, error) {
|
||||
if node := DefaultHosts.Search(host); node != nil {
|
||||
return []netip.Addr{node.Data()}, nil
|
||||
if node, ok := DefaultHosts.Search(host, false); ok {
|
||||
return node.IPs, nil
|
||||
}
|
||||
|
||||
if r != nil {
|
||||
@ -172,7 +170,7 @@ func LookupIPWithResolver(ctx context.Context, host string, r Resolver) ([]netip
|
||||
}
|
||||
return r.LookupIP(ctx, host)
|
||||
} else if DisableIPv6 {
|
||||
return LookupIPv4(ctx, host)
|
||||
return LookupIPv4WithResolver(ctx, host, r)
|
||||
}
|
||||
|
||||
if ip, err := netip.ParseAddr(host); err == nil {
|
||||
@ -202,7 +200,7 @@ func ResolveIPWithResolver(ctx context.Context, host string, r Resolver) (netip.
|
||||
} else if len(ips) == 0 {
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", ErrIPNotFound, host)
|
||||
}
|
||||
return ips[rand.Intn(len(ips))], nil
|
||||
return ips[fastrand.Intn(len(ips))], nil
|
||||
}
|
||||
|
||||
// ResolveIP with a host, return ip
|
||||
|
@ -36,12 +36,7 @@ type SnifferDispatcher struct {
|
||||
parsePureIp bool
|
||||
}
|
||||
|
||||
func (sd *SnifferDispatcher) TCPSniff(conn net.Conn, metadata *C.Metadata) {
|
||||
bufConn, ok := conn.(*N.BufferedConn)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) {
|
||||
if (metadata.Host == "" && sd.parsePureIp) || sd.forceDomain.Search(metadata.Host) != nil || (metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping) {
|
||||
port, err := strconv.ParseUint(metadata.DstPort, 10, 16)
|
||||
if err != nil {
|
||||
@ -74,7 +69,7 @@ func (sd *SnifferDispatcher) TCPSniff(conn net.Conn, metadata *C.Metadata) {
|
||||
}
|
||||
sd.rwMux.RUnlock()
|
||||
|
||||
if host, err := sd.sniffDomain(bufConn, metadata); err != nil {
|
||||
if host, err := sd.sniffDomain(conn, metadata); err != nil {
|
||||
sd.cacheSniffFailed(metadata)
|
||||
log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%s] to [%s:%s]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort)
|
||||
return
|
||||
|
@ -11,31 +11,30 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
CN "github.com/Dreamacro/clash/common/net"
|
||||
|
||||
xtls "github.com/xtls/go"
|
||||
)
|
||||
|
||||
var tlsCertificates = make([]tls.Certificate, 0)
|
||||
var trustCert, _ = x509.SystemCertPool()
|
||||
|
||||
var mutex sync.RWMutex
|
||||
var errNotMacth error = errors.New("certificate fingerprints do not match")
|
||||
|
||||
func AddCertificate(privateKey, certificate string) error {
|
||||
func AddCertificate(certificate string) error {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
if cert, err := CN.ParseCert(certificate, privateKey); err != nil {
|
||||
return err
|
||||
} else {
|
||||
tlsCertificates = append(tlsCertificates, cert)
|
||||
if certificate == "" {
|
||||
return fmt.Errorf("certificate is empty")
|
||||
}
|
||||
if ok := trustCert.AppendCertsFromPEM([]byte(certificate)); !ok {
|
||||
return fmt.Errorf("add certificate failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetCertificates() []tls.Certificate {
|
||||
mutex.RLock()
|
||||
defer mutex.RUnlock()
|
||||
return tlsCertificates
|
||||
func ResetCertificate() {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
trustCert, _ = x509.SystemCertPool()
|
||||
}
|
||||
|
||||
func verifyFingerprint(fingerprint *[32]byte) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
@ -87,10 +86,10 @@ func GetSpecifiedFingerprintTLSConfig(tlsConfig *tls.Config, fingerprint string)
|
||||
func GetGlobalTLSConfig(tlsConfig *tls.Config) *tls.Config {
|
||||
if tlsConfig == nil {
|
||||
return &tls.Config{
|
||||
Certificates: tlsCertificates,
|
||||
RootCAs: trustCert,
|
||||
}
|
||||
}
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, tlsCertificates...)
|
||||
tlsConfig.RootCAs = trustCert
|
||||
return tlsConfig
|
||||
}
|
||||
|
||||
@ -107,29 +106,12 @@ func GetSpecifiedFingerprintXTLSConfig(tlsConfig *xtls.Config, fingerprint strin
|
||||
}
|
||||
|
||||
func GetGlobalXTLSConfig(tlsConfig *xtls.Config) *xtls.Config {
|
||||
xtlsCerts := make([]xtls.Certificate, len(tlsCertificates))
|
||||
for _, cert := range tlsCertificates {
|
||||
tlsSsaList := make([]xtls.SignatureScheme, len(cert.SupportedSignatureAlgorithms))
|
||||
for _, ssa := range cert.SupportedSignatureAlgorithms {
|
||||
tlsSsa := xtls.SignatureScheme(ssa)
|
||||
tlsSsaList = append(tlsSsaList, tlsSsa)
|
||||
}
|
||||
xtlsCert := xtls.Certificate{
|
||||
Certificate: cert.Certificate,
|
||||
PrivateKey: cert.PrivateKey,
|
||||
OCSPStaple: cert.OCSPStaple,
|
||||
SignedCertificateTimestamps: cert.SignedCertificateTimestamps,
|
||||
Leaf: cert.Leaf,
|
||||
SupportedSignatureAlgorithms: tlsSsaList,
|
||||
}
|
||||
xtlsCerts = append(xtlsCerts, xtlsCert)
|
||||
}
|
||||
if tlsConfig == nil {
|
||||
return &xtls.Config{
|
||||
Certificates: xtlsCerts,
|
||||
RootCAs: trustCert,
|
||||
}
|
||||
}
|
||||
|
||||
tlsConfig.Certificates = xtlsCerts
|
||||
tlsConfig.RootCAs = trustCert
|
||||
return tlsConfig
|
||||
}
|
||||
|
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
|
||||
}
|
@ -7,7 +7,7 @@ import (
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/mroth/weightedrand/v2"
|
||||
utls "github.com/refraction-networking/utls"
|
||||
utls "github.com/sagernet/utls"
|
||||
)
|
||||
|
||||
type UConn struct {
|
||||
@ -67,13 +67,29 @@ var Fingerprints = map[string]UClientHelloID{
|
||||
"firefox": {&utls.HelloFirefox_Auto},
|
||||
"safari": {&utls.HelloSafari_Auto},
|
||||
"ios": {&utls.HelloIOS_Auto},
|
||||
"randomized": {&utls.HelloRandomized},
|
||||
"android": {&utls.HelloAndroid_11_OkHttp},
|
||||
"edge": {&utls.HelloEdge_Auto},
|
||||
"360": {&utls.Hello360_Auto},
|
||||
"qq": {&utls.HelloQQ_Auto},
|
||||
"random": {nil},
|
||||
"randomized": {nil},
|
||||
}
|
||||
|
||||
func init() {
|
||||
weights := utls.DefaultWeights
|
||||
weights.TLSVersMax_Set_VersionTLS13 = 1
|
||||
weights.FirstKeyShare_Set_CurveP256 = 0
|
||||
randomized := utls.HelloRandomized
|
||||
randomized.Seed, _ = utls.NewPRNGSeed()
|
||||
randomized.Weights = &weights
|
||||
Fingerprints["randomized"] = UClientHelloID{&randomized}
|
||||
}
|
||||
|
||||
func copyConfig(c *tls.Config) *utls.Config {
|
||||
return &utls.Config{
|
||||
RootCAs: c.RootCAs,
|
||||
ServerName: c.ServerName,
|
||||
NextProtos: c.NextProtos,
|
||||
InsecureSkipVerify: c.InsecureSkipVerify,
|
||||
VerifyPeerCertificate: c.VerifyPeerCertificate,
|
||||
}
|
||||
|
139
config/config.go
139
config/config.go
@ -4,12 +4,11 @@ import (
|
||||
"container/list"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"net"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -26,6 +25,7 @@ import (
|
||||
"github.com/Dreamacro/clash/component/geodata"
|
||||
"github.com/Dreamacro/clash/component/geodata/router"
|
||||
P "github.com/Dreamacro/clash/component/process"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
SNIFF "github.com/Dreamacro/clash/component/sniffer"
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
@ -92,6 +92,7 @@ type DNS struct {
|
||||
Enable bool `yaml:"enable"`
|
||||
PreferH3 bool `yaml:"prefer-h3"`
|
||||
IPv6 bool `yaml:"ipv6"`
|
||||
IPv6Timeout uint `yaml:"ipv6-timeout"`
|
||||
NameServer []dns.NameServer `yaml:"nameserver"`
|
||||
Fallback []dns.NameServer `yaml:"fallback"`
|
||||
FallbackFilter FallbackFilter `yaml:"fallback-filter"`
|
||||
@ -99,7 +100,7 @@ type DNS struct {
|
||||
EnhancedMode C.DNSMode `yaml:"enhanced-mode"`
|
||||
DefaultNameserver []dns.NameServer `yaml:"default-nameserver"`
|
||||
FakeIPRange *fakeip.Pool
|
||||
Hosts *trie.DomainTrie[netip.Addr]
|
||||
Hosts *trie.DomainTrie[resolver.HostValue]
|
||||
NameServerPolicy map[string][]dns.NameServer
|
||||
ProxyServerNameserver []dns.NameServer
|
||||
}
|
||||
@ -120,13 +121,9 @@ type Profile struct {
|
||||
}
|
||||
|
||||
type TLS struct {
|
||||
RawCert `yaml:",inline"`
|
||||
CustomTrustCert []RawCert `yaml:"custom-certifactes"`
|
||||
}
|
||||
|
||||
type RawCert struct {
|
||||
Certificate string `yaml:"certificate"`
|
||||
PrivateKey string `yaml:"private-key"`
|
||||
Certificate string `yaml:"certificate"`
|
||||
PrivateKey string `yaml:"private-key"`
|
||||
CustomTrustCert []string `yaml:"custom-certifactes"`
|
||||
}
|
||||
|
||||
// IPTables config
|
||||
@ -157,7 +154,7 @@ type Config struct {
|
||||
IPTables *IPTables
|
||||
DNS *DNS
|
||||
Experimental *Experimental
|
||||
Hosts *trie.DomainTrie[netip.Addr]
|
||||
Hosts *trie.DomainTrie[resolver.HostValue]
|
||||
Profile *Profile
|
||||
Rules []C.Rule
|
||||
SubRules map[string][]C.Rule
|
||||
@ -175,6 +172,7 @@ type RawDNS struct {
|
||||
Enable bool `yaml:"enable"`
|
||||
PreferH3 bool `yaml:"prefer-h3"`
|
||||
IPv6 bool `yaml:"ipv6"`
|
||||
IPv6Timeout uint `yaml:"ipv6-timeout"`
|
||||
UseHosts bool `yaml:"use-hosts"`
|
||||
NameServer []string `yaml:"nameserver"`
|
||||
Fallback []string `yaml:"fallback"`
|
||||
@ -267,7 +265,7 @@ type RawConfig struct {
|
||||
Sniffer RawSniffer `yaml:"sniffer"`
|
||||
ProxyProvider map[string]map[string]any `yaml:"proxy-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"`
|
||||
Tun RawTun `yaml:"tun"`
|
||||
TuicServer RawTuicServer `yaml:"tuic-server"`
|
||||
@ -341,7 +339,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
UnifiedDelay: false,
|
||||
Authentication: []string{},
|
||||
LogLevel: log.INFO,
|
||||
Hosts: map[string]string{},
|
||||
Hosts: map[string]any{},
|
||||
Rule: []string{},
|
||||
Proxy: []map[string]any{},
|
||||
ProxyGroup: []map[string]any{},
|
||||
@ -381,6 +379,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
Enable: false,
|
||||
IPv6: false,
|
||||
UseHosts: true,
|
||||
IPv6Timeout: 100,
|
||||
EnhancedMode: C.DNSMapping,
|
||||
FakeIPRange: "198.18.0.1/16",
|
||||
FallbackFilter: RawFallbackFilter{
|
||||
@ -419,9 +418,9 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
StoreSelected: true,
|
||||
},
|
||||
GeoXUrl: RawGeoXUrl{
|
||||
GeoIp: "https://ghproxy.com/https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geoip.dat",
|
||||
Mmdb: "https://ghproxy.com/https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb",
|
||||
GeoSite: "https://ghproxy.com/https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geosite.dat",
|
||||
Mmdb: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/country.mmdb",
|
||||
GeoIp: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat",
|
||||
GeoSite: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat",
|
||||
},
|
||||
}
|
||||
|
||||
@ -447,6 +446,11 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
||||
}
|
||||
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)
|
||||
proxies, providers, err := parseProxies(rawCfg)
|
||||
if err != nil {
|
||||
@ -522,11 +526,6 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
||||
elapsedTime := time.Since(startTime) / time.Millisecond // duration in ms
|
||||
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
|
||||
}
|
||||
|
||||
@ -828,21 +827,47 @@ func parseRules(rulesConfig []string, proxies map[string]C.Proxy, subRules map[s
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
func parseHosts(cfg *RawConfig) (*trie.DomainTrie[netip.Addr], error) {
|
||||
tree := trie.New[netip.Addr]()
|
||||
func parseHosts(cfg *RawConfig) (*trie.DomainTrie[resolver.HostValue], error) {
|
||||
tree := trie.New[resolver.HostValue]()
|
||||
|
||||
// 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())
|
||||
}
|
||||
|
||||
if len(cfg.Hosts) != 0 {
|
||||
for domain, ipStr := range cfg.Hosts {
|
||||
ip, err := netip.ParseAddr(ipStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s is not a valid IP", ipStr)
|
||||
for domain, anyValue := range cfg.Hosts {
|
||||
if str, ok := anyValue.(string); ok && str == "clash" {
|
||||
if addrs, err := net.InterfaceAddrs(); err != nil {
|
||||
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()
|
||||
@ -960,26 +985,39 @@ func parsePureDNSServer(server string) string {
|
||||
}
|
||||
func parseNameServerPolicy(nsPolicy map[string]any, preferH3 bool) (map[string][]dns.NameServer, error) {
|
||||
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 {
|
||||
var (
|
||||
nameservers []dns.NameServer
|
||||
err error
|
||||
)
|
||||
|
||||
switch reflect.TypeOf(server).Kind() {
|
||||
case reflect.Slice, reflect.Array:
|
||||
origin := reflect.ValueOf(server)
|
||||
servers := make([]string, 0)
|
||||
for i := 0; i < origin.Len(); i++ {
|
||||
servers = append(servers, fmt.Sprintf("%v", origin.Index(i)))
|
||||
for k, v := range nsPolicy {
|
||||
if strings.Contains(k, "geosite:") {
|
||||
subkeys := strings.Split(k, ":")
|
||||
subkeys = subkeys[1:]
|
||||
subkeys = strings.Split(subkeys[0], ",")
|
||||
//log.Infoln("subkeys:%+v", subkeys)
|
||||
for _, subkey := range subkeys {
|
||||
newKey := "geosite:" + subkey
|
||||
//log.Infoln("newKey:%+v", newKey)
|
||||
updatedPolicy[newKey] = v
|
||||
}
|
||||
nameservers, err = parseNameServer(servers, preferH3)
|
||||
case reflect.String:
|
||||
nameservers, err = parseNameServer([]string{fmt.Sprintf("%v", server)}, preferH3)
|
||||
default:
|
||||
return nil, errors.New("server format error, must be string or array")
|
||||
} else if re.MatchString(k) {
|
||||
subkeys := strings.Split(k, ",")
|
||||
//log.Infoln("subkeys:%+v", subkeys)
|
||||
for _, subkey := range subkeys {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@ -1042,7 +1080,7 @@ func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainM
|
||||
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
|
||||
if cfg.Enable && len(cfg.NameServer) == 0 {
|
||||
return nil, fmt.Errorf("if DNS configuration is turned on, NameServer cannot be empty")
|
||||
@ -1052,6 +1090,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[netip.Addr], rules []C.R
|
||||
Enable: cfg.Enable,
|
||||
Listen: cfg.Listen,
|
||||
PreferH3: cfg.PreferH3,
|
||||
IPv6Timeout: cfg.IPv6Timeout,
|
||||
IPv6: cfg.IPv6,
|
||||
EnhancedMode: cfg.EnhancedMode,
|
||||
FallbackFilter: FallbackFilter{
|
||||
@ -1259,8 +1298,10 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Deprecated: Use Sniff instead
|
||||
log.Warnln("Deprecated: Use Sniff instead")
|
||||
if sniffer.Enable {
|
||||
// Deprecated: Use Sniff instead
|
||||
log.Warnln("Deprecated: Use Sniff instead")
|
||||
}
|
||||
globalPorts, err := parsePortRange(snifferRaw.Ports)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -3,6 +3,8 @@ package constant
|
||||
import (
|
||||
"net"
|
||||
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
@ -13,7 +15,7 @@ type PlainContext interface {
|
||||
type ConnContext interface {
|
||||
PlainContext
|
||||
Metadata() *Metadata
|
||||
Conn() net.Conn
|
||||
Conn() *N.BufferedConn
|
||||
}
|
||||
|
||||
type PacketConnContext interface {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"net"
|
||||
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
@ -12,11 +13,11 @@ import (
|
||||
type ConnContext struct {
|
||||
id uuid.UUID
|
||||
metadata *C.Metadata
|
||||
conn net.Conn
|
||||
conn *N.BufferedConn
|
||||
}
|
||||
|
||||
func NewConnContext(conn net.Conn, metadata *C.Metadata) *ConnContext {
|
||||
id, _ := uuid.NewV4()
|
||||
id, _ := utils.UnsafeUUIDGenerator.NewV4()
|
||||
|
||||
return &ConnContext{
|
||||
id: id,
|
||||
@ -36,6 +37,6 @@ func (c *ConnContext) Metadata() *C.Metadata {
|
||||
}
|
||||
|
||||
// Conn implement C.ConnContext Conn
|
||||
func (c *ConnContext) Conn() net.Conn {
|
||||
func (c *ConnContext) Conn() *N.BufferedConn {
|
||||
return c.conn
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/miekg/dns"
|
||||
@ -22,7 +23,7 @@ type DNSContext struct {
|
||||
}
|
||||
|
||||
func NewDNSContext(ctx context.Context, msg *dns.Msg) *DNSContext {
|
||||
id, _ := uuid.NewV4()
|
||||
id, _ := utils.UnsafeUUIDGenerator.NewV4()
|
||||
return &DNSContext{
|
||||
Context: ctx,
|
||||
|
||||
|
@ -3,6 +3,7 @@ package context
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
@ -15,7 +16,7 @@ type PacketConnContext struct {
|
||||
}
|
||||
|
||||
func NewPacketConnContext(metadata *C.Metadata) *PacketConnContext {
|
||||
id, _ := uuid.NewV4()
|
||||
id, _ := utils.UnsafeUUIDGenerator.NewV4()
|
||||
return &PacketConnContext{
|
||||
id: id,
|
||||
metadata: metadata,
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
@ -16,6 +15,7 @@ import (
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
type client struct {
|
||||
@ -68,7 +68,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
|
||||
} else if len(ips) == 0 {
|
||||
return nil, fmt.Errorf("%w: %s", resolver.ErrIPNotFound, c.host)
|
||||
}
|
||||
ip = ips[rand.Intn(len(ips))]
|
||||
ip = ips[fastrand.Intn(len(ips))]
|
||||
}
|
||||
|
||||
network := "udp"
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
"github.com/Dreamacro/clash/common/nnip"
|
||||
"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"
|
||||
"github.com/Dreamacro/clash/context"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
@ -21,7 +21,7 @@ type (
|
||||
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(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) {
|
||||
q := r.Question[0]
|
||||
@ -31,40 +31,68 @@ func withHosts(hosts *trie.DomainTrie[netip.Addr], mapping *cache.LruCache[netip
|
||||
}
|
||||
|
||||
host := strings.TrimRight(q.Name, ".")
|
||||
|
||||
record := hosts.Search(host)
|
||||
if record == nil {
|
||||
handleCName := func(resp *D.Msg, domain string) {
|
||||
rr := &D.CNAME{}
|
||||
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)
|
||||
}
|
||||
|
||||
ip := record.Data()
|
||||
msg := r.Copy()
|
||||
|
||||
if ip.Is4() && q.Qtype == D.TypeA {
|
||||
rr := &D.A{}
|
||||
rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeA, Class: D.ClassINET, Ttl: 10}
|
||||
rr.A = ip.AsSlice()
|
||||
|
||||
msg.Answer = []D.RR{rr}
|
||||
} else if q.Qtype == D.TypeAAAA {
|
||||
rr := &D.AAAA{}
|
||||
rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeAAAA, Class: D.ClassINET, Ttl: 10}
|
||||
ip := ip.As16()
|
||||
rr.AAAA = ip[:]
|
||||
msg.Answer = []D.RR{rr}
|
||||
} else {
|
||||
return next(ctx, r)
|
||||
handleIPs := func() {
|
||||
for _, ipAddr := range record.IPs {
|
||||
if ipAddr.Is4() && q.Qtype == D.TypeA {
|
||||
rr := &D.A{}
|
||||
rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeA, Class: D.ClassINET, Ttl: 10}
|
||||
rr.A = ipAddr.AsSlice()
|
||||
msg.Answer = append(msg.Answer, rr)
|
||||
if mapping != nil {
|
||||
mapping.SetWithExpire(ipAddr, host, time.Now().Add(time.Second*10))
|
||||
}
|
||||
} else if q.Qtype == D.TypeAAAA {
|
||||
rr := &D.AAAA{}
|
||||
rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeAAAA, Class: D.ClassINET, Ttl: 10}
|
||||
ip := ipAddr.As16()
|
||||
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 {
|
||||
mapping.SetWithExpire(ip, host, time.Now().Add(time.Second*10))
|
||||
switch q.Qtype {
|
||||
case D.TypeA:
|
||||
handleIPs()
|
||||
case D.TypeAAAA:
|
||||
handleIPs()
|
||||
case D.TypeCNAME:
|
||||
handleCName(r, record.Domain)
|
||||
default:
|
||||
return next(ctx, r)
|
||||
}
|
||||
|
||||
ctx.SetType(context.DNSTypeHost)
|
||||
msg.SetRcode(r, D.RcodeSuccess)
|
||||
msg.Authoritative = true
|
||||
msg.RecursionAvailable = true
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
}
|
||||
@ -149,6 +177,7 @@ func withFakeIP(fakePool *fakeip.Pool) middleware {
|
||||
func withResolver(resolver *Resolver) handler {
|
||||
return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) {
|
||||
ctx.SetType(context.DNSTypeRaw)
|
||||
|
||||
q := r.Question[0]
|
||||
|
||||
// return a empty AAAA msg when ipv6 disabled
|
||||
@ -183,7 +212,7 @@ func NewHandler(resolver *Resolver, mapper *ResolverEnhancer) handler {
|
||||
middlewares := []middleware{}
|
||||
|
||||
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 {
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"time"
|
||||
@ -20,6 +19,7 @@ import (
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
"golang.org/x/sync/singleflight"
|
||||
)
|
||||
|
||||
@ -42,7 +42,8 @@ type geositePolicyRecord struct {
|
||||
|
||||
type Resolver struct {
|
||||
ipv6 bool
|
||||
hosts *trie.DomainTrie[netip.Addr]
|
||||
ipv6Timeout time.Duration
|
||||
hosts *trie.DomainTrie[resolver.HostValue]
|
||||
main []dnsClient
|
||||
fallback []dnsClient
|
||||
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)
|
||||
|
||||
var waitIPv6 *time.Timer
|
||||
if r != nil {
|
||||
waitIPv6 = time.NewTimer(r.ipv6Timeout)
|
||||
} else {
|
||||
waitIPv6 = time.NewTimer(100 * time.Millisecond)
|
||||
}
|
||||
defer waitIPv6.Stop()
|
||||
select {
|
||||
case ipv6s, open := <-ch:
|
||||
if !open && err != nil {
|
||||
return nil, resolver.ErrIPNotFound
|
||||
}
|
||||
ips = append(ips, ipv6s...)
|
||||
case <-time.After(30 * time.Millisecond):
|
||||
case <-waitIPv6.C:
|
||||
// wait ipv6 result
|
||||
}
|
||||
|
||||
@ -113,7 +120,7 @@ func (r *Resolver) ResolveIP(ctx context.Context, host string) (ip netip.Addr, e
|
||||
} else if len(ips) == 0 {
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", resolver.ErrIPNotFound, host)
|
||||
}
|
||||
return ips[rand.Intn(len(ips))], nil
|
||||
return ips[fastrand.Intn(len(ips))], nil
|
||||
}
|
||||
|
||||
// LookupIPv4 request with TypeA
|
||||
@ -129,7 +136,7 @@ func (r *Resolver) ResolveIPv4(ctx context.Context, host string) (ip netip.Addr,
|
||||
} else if len(ips) == 0 {
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", resolver.ErrIPNotFound, host)
|
||||
}
|
||||
return ips[rand.Intn(len(ips))], nil
|
||||
return ips[fastrand.Intn(len(ips))], nil
|
||||
}
|
||||
|
||||
// LookupIPv6 request with TypeAAAA
|
||||
@ -145,7 +152,7 @@ func (r *Resolver) ResolveIPv6(ctx context.Context, host string) (ip netip.Addr,
|
||||
} else if len(ips) == 0 {
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", resolver.ErrIPNotFound, host)
|
||||
}
|
||||
return ips[rand.Intn(len(ips))], nil
|
||||
return ips[fastrand.Intn(len(ips))], nil
|
||||
}
|
||||
|
||||
func (r *Resolver) shouldIPFallback(ip netip.Addr) bool {
|
||||
@ -419,24 +426,27 @@ type Config struct {
|
||||
Default []NameServer
|
||||
ProxyServer []NameServer
|
||||
IPv6 bool
|
||||
IPv6Timeout uint
|
||||
EnhancedMode C.DNSMode
|
||||
FallbackFilter FallbackFilter
|
||||
Pool *fakeip.Pool
|
||||
Hosts *trie.DomainTrie[netip.Addr]
|
||||
Hosts *trie.DomainTrie[resolver.HostValue]
|
||||
Policy map[string][]NameServer
|
||||
}
|
||||
|
||||
func NewResolver(config Config) *Resolver {
|
||||
defaultResolver := &Resolver{
|
||||
main: transform(config.Default, nil),
|
||||
lruCache: cache.New(cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true)),
|
||||
main: transform(config.Default, nil),
|
||||
lruCache: cache.New(cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true)),
|
||||
ipv6Timeout: time.Duration(config.IPv6Timeout) * time.Millisecond,
|
||||
}
|
||||
|
||||
r := &Resolver{
|
||||
ipv6: config.IPv6,
|
||||
main: transform(config.Main, defaultResolver),
|
||||
lruCache: cache.New(cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true)),
|
||||
hosts: config.Hosts,
|
||||
ipv6: config.IPv6,
|
||||
main: transform(config.Main, defaultResolver),
|
||||
lruCache: cache.New(cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true)),
|
||||
hosts: config.Hosts,
|
||||
ipv6Timeout: time.Duration(config.IPv6Timeout) * time.Millisecond,
|
||||
}
|
||||
|
||||
if len(config.Fallback) != 0 {
|
||||
@ -502,11 +512,12 @@ func NewResolver(config Config) *Resolver {
|
||||
|
||||
func NewProxyServerHostResolver(old *Resolver) *Resolver {
|
||||
r := &Resolver{
|
||||
ipv6: old.ipv6,
|
||||
main: old.proxyServer,
|
||||
lruCache: old.lruCache,
|
||||
hosts: old.hosts,
|
||||
policy: old.policy,
|
||||
ipv6: old.ipv6,
|
||||
main: old.proxyServer,
|
||||
lruCache: old.lruCache,
|
||||
hosts: old.hosts,
|
||||
policy: old.policy,
|
||||
ipv6Timeout: old.ipv6Timeout,
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ func setMsgTTL(msg *D.Msg, ttl uint32) {
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -1,26 +1,25 @@
|
||||
#!/bin/sh
|
||||
os="clash.meta-linux-"
|
||||
arch=`uname -m`
|
||||
case $arch in
|
||||
"x86_64")
|
||||
case $TARGETPLATFORM in
|
||||
"linux/amd64")
|
||||
arch="amd64-compatible"
|
||||
;;
|
||||
"x86")
|
||||
arch="386-cgo"
|
||||
"linux/386")
|
||||
arch="386"
|
||||
;;
|
||||
"aarch64")
|
||||
"linux/arm64")
|
||||
arch="arm64"
|
||||
;;
|
||||
"armv7l")
|
||||
"linux/arm/v7")
|
||||
arch="armv7"
|
||||
;;
|
||||
"riscv64")
|
||||
arch="riscv64-cgo"
|
||||
arch="riscv64"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown architecture"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
file_name="$os$arch"
|
||||
file_name="$os$arch-$(cat bin/version.txt)"
|
||||
echo $file_name
|
538
docs/config.yaml
538
docs/config.yaml
@ -7,22 +7,17 @@ mixed-port: 10801 # HTTP(S) 和 SOCKS 代理混合端口
|
||||
# tproxy-port: 7893
|
||||
|
||||
allow-lan: true # 允许局域网连接
|
||||
bind-address: "*" # 绑定IP地址,仅作用于 allow-lan 为 true,'*'表示所有地址
|
||||
bind-address: "*" # 绑定 IP 地址,仅作用于 allow-lan 为 true,'*'表示所有地址
|
||||
|
||||
# find-process-mode has 3 values: always, strict, off
|
||||
# find-process-mode has 3 values:always, strict, off
|
||||
# - always, 开启,强制匹配所有进程
|
||||
# - strict, 默认,由clash判断是否开启
|
||||
# - strict, 默认,由 clash 判断是否开启
|
||||
# - off, 不匹配进程,推荐在路由器上使用此模式
|
||||
find-process-mode: strict
|
||||
|
||||
# global-client-fingerprint:全局TLS指纹,优先低于proxy内的 client-fingerprint
|
||||
# accepts "chrome","firefox","safari","ios","random","none" options.
|
||||
# Utls is currently support TLS transport in TCP/grpc/WS/HTTP for VLESS/Vmess and trojan.
|
||||
global-client-fingerprint: chrome
|
||||
|
||||
mode: rule
|
||||
|
||||
#自定义 geox-url
|
||||
#自定义 geodata url
|
||||
geox-url:
|
||||
geoip: "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat"
|
||||
geosite: "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat"
|
||||
@ -32,16 +27,30 @@ log-level: debug # 日志等级 silent/error/warning/info/debug
|
||||
|
||||
ipv6: true # 开启 IPv6 总开关,关闭阻断所有 IPv6 链接和屏蔽 DNS 请求 AAAA 记录
|
||||
|
||||
tls:
|
||||
certificate: string # 证书 PEM 格式,或者 证书的路径
|
||||
private-key: string # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
custom-certifactes:
|
||||
- |
|
||||
-----BEGIN CERTIFICATE-----
|
||||
format/pem...
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
external-controller: 0.0.0.0:9093 # RESTful API 监听地址
|
||||
external-controller-tls: 0.0.0.0:9443 # RESTful API HTTPS 监听地址,需要配置 tls 部分配置文件
|
||||
# secret: "123456" # `Authorization: Bearer ${secret}`
|
||||
# secret: "123456" # `Authorization:Bearer ${secret}`
|
||||
|
||||
# tcp-concurrent: true # TCP并发连接所有IP, 将使用最快握手的TCP
|
||||
external-ui: /path/to/ui/folder # 配置WEB UI目录,使用http://{{external-controller}}/ui 访问
|
||||
# tcp-concurrent: true # TCP 并发连接所有 IP, 将使用最快握手的 TCP
|
||||
external-ui: /path/to/ui/folder # 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问
|
||||
|
||||
# interface-name: en0 # 设置出口网卡
|
||||
|
||||
# routing-mark: 6666 # 配置 fwmark 仅用于Linux
|
||||
# 全局 TLS 指纹,优先低于 proxy 内的 client-fingerprint
|
||||
# 可选: "chrome","firefox","safari","ios","random","none" options.
|
||||
# Utls is currently support TLS transport in TCP/grpc/WS/HTTP for VLESS/Vmess and trojan.
|
||||
global-client-fingerprint: chrome
|
||||
|
||||
# routing-mark:6666 # 配置 fwmark 仅用于 Linux
|
||||
experimental:
|
||||
|
||||
# 类似于 /etc/hosts, 仅支持配置单个 IP
|
||||
@ -49,6 +58,15 @@ hosts:
|
||||
# '*.clash.dev': 127.0.0.1
|
||||
# '.dev': 127.0.0.1
|
||||
# 'alpha.clash.dev': '::1'
|
||||
# test.com: [1.1.1.1, 2.2.2.2]
|
||||
# clash.lan: clash # clash 为特别字段,将加入本地所有网卡的地址
|
||||
# baidu.com: google.com # 只允许配置一个别名
|
||||
|
||||
profile: # 存储 select 选择记录
|
||||
store-selected: false
|
||||
|
||||
# 持久化 fake-ip
|
||||
store-fake-ip: true
|
||||
|
||||
# Tun 配置
|
||||
tun:
|
||||
@ -75,10 +93,10 @@ tun:
|
||||
#- 1000
|
||||
# exclude_uid_range: # 排除路由的的用户范围
|
||||
# - 1000-99999
|
||||
|
||||
|
||||
# Android 用户和应用规则仅在 Android 下被支持
|
||||
# 并且需要 auto_route
|
||||
|
||||
|
||||
# include_android_user: # 限制被路由的 Android 用户
|
||||
# - 0
|
||||
# - 10
|
||||
@ -105,15 +123,13 @@ sniffer:
|
||||
# 是否使用嗅探结果作为实际访问,默认 true
|
||||
# 全局配置,优先级低于 sniffer.sniff 实际配置
|
||||
override-destination: false
|
||||
sniff:
|
||||
# TLS 默认如果不配置 ports 默认嗅探 443
|
||||
sniff: # TLS 默认如果不配置 ports 默认嗅探 443
|
||||
TLS:
|
||||
# ports: [443, 8443]
|
||||
|
||||
|
||||
# 默认嗅探 80
|
||||
HTTP:
|
||||
# 需要嗅探的端口
|
||||
|
||||
HTTP: # 需要嗅探的端口
|
||||
|
||||
ports: [80, 8080-8880]
|
||||
# 可覆盖 sniffer.override-destination
|
||||
override-destination: true
|
||||
@ -128,7 +144,7 @@ sniffer:
|
||||
- tls
|
||||
- http
|
||||
# 强制对此域名进行嗅探
|
||||
|
||||
|
||||
# 仅对白名单中的端口进行嗅探,默认为 443,80
|
||||
# 已废弃,若 sniffer.sniff 配置则此项无效
|
||||
port-whitelist:
|
||||
@ -136,27 +152,8 @@ sniffer:
|
||||
- "443"
|
||||
# - 8000-9999
|
||||
|
||||
# shadowsocks,vmess 入口配置(传入流量将和socks,mixed等入口一样按照mode所指定的方式进行匹配处理)
|
||||
# ss-config: ss://2022-blake3-aes-256-gcm:vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg=@:23456
|
||||
# vmess-config: vmess://1:9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68@:12345
|
||||
|
||||
# tuic服务器入口(传入流量将和socks,mixed等入口一样按照mode所指定的方式进行匹配处理)
|
||||
#tuic-server:
|
||||
# enable: true
|
||||
# listen: 127.0.0.1:10443
|
||||
# token:
|
||||
# - TOKEN
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
# congestion-controller: bbr
|
||||
# max-idle-time: 15000
|
||||
# authentication-timeout: 1000
|
||||
# alpn:
|
||||
# - h3
|
||||
# max-udp-relay-packet-size: 1500
|
||||
|
||||
tunnels:
|
||||
# one line config
|
||||
tunnels: # one line config
|
||||
- tcp/udp,127.0.0.1:6553,114.114.114.114:53,proxy
|
||||
- tcp,127.0.0.1:6666,rds.mysql.com:3306,vpn
|
||||
# full yaml config
|
||||
@ -165,12 +162,6 @@ tunnels:
|
||||
target: target.com
|
||||
proxy: proxy
|
||||
|
||||
profile:
|
||||
# 存储select选择记录
|
||||
store-selected: false
|
||||
|
||||
# 持久化fake-ip
|
||||
store-fake-ip: true
|
||||
|
||||
# DNS配置
|
||||
dns:
|
||||
@ -178,7 +169,7 @@ dns:
|
||||
prefer-h3: true # 开启 DoH 支持 HTTP/3,将并发尝试
|
||||
listen: 0.0.0.0:53 # 开启 DNS 服务器监听
|
||||
# ipv6: false # false 将返回 AAAA 的空结果
|
||||
|
||||
# ipv6-timeout: 300 # 单位:ms,内部双栈并发时,向上游查询 AAAA 时,等待 AAAA 的时间,默认 100ms
|
||||
# 用于解析 nameserver,fallback 以及其他DNS服务器配置的,DNS 服务域名
|
||||
# 只能使用纯 IP 地址,可使用加密 DNS
|
||||
default-nameserver:
|
||||
@ -187,16 +178,16 @@ dns:
|
||||
- tls://1.12.12.12:853
|
||||
- tls://223.5.5.5:853
|
||||
enhanced-mode: fake-ip # or redir-host
|
||||
|
||||
|
||||
fake-ip-range: 198.18.0.1/16 # fake-ip 池设置
|
||||
|
||||
|
||||
# use-hosts: true # 查询 hosts
|
||||
|
||||
|
||||
# 配置不使用fake-ip的域名
|
||||
# fake-ip-filter:
|
||||
# - '*.lan'
|
||||
# - localhost.ptlogin2.qq.com
|
||||
|
||||
|
||||
# DNS主要域名配置
|
||||
# 支持 UDP,TCP,DoT,DoH,DoQ
|
||||
# 这部分为主要 DNS 配置,影响所有直连,确保使用对大陆解析精准的 DNS
|
||||
@ -210,20 +201,20 @@ dns:
|
||||
- dhcp://en0 # dns from dhcp
|
||||
- quic://dns.adguard.com:784 # DNS over QUIC
|
||||
# - '8.8.8.8#en0' # 兼容指定DNS出口网卡
|
||||
|
||||
|
||||
# 当配置 fallback 时,会查询 nameserver 中返回的 IP 是否为 CN,非必要配置
|
||||
# 当不是 CN,则使用 fallback 中的 DNS 查询结果
|
||||
# 确保配置 fallback 时能够正常查询
|
||||
# fallback:
|
||||
# - tcp://1.1.1.1
|
||||
# - 'tcp://1.1.1.1#ProxyGroupName' # 指定 DNS 过代理查询,ProxyGroupName 为策略组名或节点名,过代理配置优先于配置出口网卡,当找不到策略组或节点名则设置为出口网卡
|
||||
|
||||
|
||||
# 专用于节点域名解析的 DNS 服务器,非必要配置项
|
||||
# 配置服务器若查询失败将使用 nameserver,非并发查询
|
||||
# proxy-server-nameserver:
|
||||
# - https://dns.google/dns-query
|
||||
# - tls://one.one.one.one
|
||||
|
||||
|
||||
# 配置 fallback 使用条件
|
||||
# fallback-filter:
|
||||
# geoip: true # 配置是否使用 geoip
|
||||
@ -238,14 +229,54 @@ dns:
|
||||
# - '+.google.com'
|
||||
# - '+.facebook.com'
|
||||
# - '+.youtube.com'
|
||||
|
||||
|
||||
# 配置查询域名使用的 DNS 服务器
|
||||
nameserver-policy:
|
||||
# 'www.baidu.com': '114.114.114.114'
|
||||
# '+.internal.crop.com': '10.0.0.1'
|
||||
"geosite:cn": "https://doh.pub/dns-query"
|
||||
"www.baidu.com": [https://doh.pub/dns-query,https://dns.alidns.com/dns-query]
|
||||
proxies:
|
||||
"geosite:cn,private,apple":
|
||||
- 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
|
||||
- name: "socks"
|
||||
type: socks5
|
||||
server: server
|
||||
port: 443
|
||||
# username: username
|
||||
# password: password
|
||||
# tls: true
|
||||
# fingerprint: xxxx
|
||||
# skip-cert-verify: true
|
||||
# udp: true
|
||||
# ip-version: ipv6
|
||||
|
||||
# http
|
||||
- name: "http"
|
||||
type: http
|
||||
server: server
|
||||
port: 443
|
||||
# username: username
|
||||
# password: password
|
||||
# tls: true # https
|
||||
# skip-cert-verify: true
|
||||
# sni: custom.com
|
||||
# fingerprint: xxxx # 同 experimental.fingerprints 使用 sha256 指纹,配置协议独立的指纹,将忽略 experimental.fingerprints
|
||||
# ip-version: dual
|
||||
|
||||
# Snell
|
||||
# Beware that there's currently no UDP support yet
|
||||
- name: "snell"
|
||||
type: snell
|
||||
server: server
|
||||
port: 44046
|
||||
psk: yourpsk
|
||||
# version: 2
|
||||
# obfs-opts:
|
||||
# mode: http # or tls
|
||||
# host: bing.com
|
||||
|
||||
# Shadowsocks
|
||||
# cipher支持:
|
||||
# aes-128-gcm aes-192-gcm aes-256-gcm
|
||||
@ -268,6 +299,7 @@ proxies:
|
||||
# UDP 则为双栈解析,获取结果中的第一个 IPv4
|
||||
# ipv6-prefer 同 ipv4-prefer
|
||||
# 现有协议都支持此参数,TCP 效果仅在开启 tcp-concurrent 生效
|
||||
|
||||
- name: "ss2"
|
||||
type: ss
|
||||
server: server
|
||||
@ -278,7 +310,7 @@ proxies:
|
||||
plugin-opts:
|
||||
mode: tls # or http
|
||||
# host: bing.com
|
||||
|
||||
|
||||
- name: "ss3"
|
||||
type: ss
|
||||
server: server
|
||||
@ -288,28 +320,67 @@ proxies:
|
||||
plugin: v2ray-plugin
|
||||
plugin-opts:
|
||||
mode: websocket # no QUIC now
|
||||
# tls: true # wss
|
||||
# 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||
# 配置指纹将实现 SSL Pining 效果
|
||||
# fingerprint: xxxx
|
||||
# skip-cert-verify: true
|
||||
# host: bing.com
|
||||
# path: "/"
|
||||
# mux: true
|
||||
# headers:
|
||||
# custom: value
|
||||
|
||||
- name: "ss4"
|
||||
# tls: true # wss
|
||||
# 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||
# 配置指纹将实现 SSL Pining 效果
|
||||
# fingerprint: xxxx
|
||||
# skip-cert-verify: true
|
||||
# host: bing.com
|
||||
# path: "/"
|
||||
# mux: true
|
||||
# headers:
|
||||
# custom: value
|
||||
|
||||
- name: "ss4-shadow-tls"
|
||||
type: ss
|
||||
server: server
|
||||
port: 443
|
||||
cipher: chacha20-ietf-poly1305
|
||||
password: "password"
|
||||
plugin: shadow-tls
|
||||
client-fingerprint: chrome
|
||||
plugin-opts:
|
||||
host: "cloud.tencent.com"
|
||||
password: "shadow_tls_password"
|
||||
version: 2 # support 1/2/3
|
||||
|
||||
- name: "ss-restls-tls13"
|
||||
type: ss
|
||||
server: [YOUR_SERVER_IP]
|
||||
port: 443
|
||||
cipher: chacha20-ietf-poly1305
|
||||
password: [YOUR_SS_PASSWORD]
|
||||
client-fingerprint: chrome # One of: chrome, ios, firefox or safari
|
||||
# 可以是chrome, ios, firefox, safari中的一个
|
||||
plugin: restls
|
||||
plugin-opts:
|
||||
host: "www.microsoft.com" # Must be a TLS 1.3 server
|
||||
# 应当是一个TLS 1.3 服务器
|
||||
password: [YOUR_RESTLS_PASSWORD]
|
||||
version-hint: "tls13"
|
||||
# Control your post-handshake traffic through restls-script
|
||||
# Hide proxy behaviors like "tls in tls".
|
||||
# see https://github.com/3andne/restls/blob/main/Restls-Script:%20Hide%20Your%20Proxy%20Traffic%20Behavior.md
|
||||
# 用restls剧本来控制握手后的行为,隐藏"tls in tls"等特征
|
||||
# 详情:https://github.com/3andne/restls/blob/main/Restls-Script:%20%E9%9A%90%E8%97%8F%E4%BD%A0%E7%9A%84%E4%BB%A3%E7%90%86%E8%A1%8C%E4%B8%BA.md
|
||||
restls-script: "300?100<1,400~100,350~100,600~100,300~200,300~100"
|
||||
|
||||
- name: "ss-restls-tls12"
|
||||
type: ss
|
||||
server: [YOUR_SERVER_IP]
|
||||
port: 443
|
||||
cipher: chacha20-ietf-poly1305
|
||||
password: [YOUR_SS_PASSWORD]
|
||||
client-fingerprint: chrome # One of: chrome, ios, firefox or safari
|
||||
# 可以是chrome, ios, firefox, safari中的一个
|
||||
plugin: restls
|
||||
plugin-opts:
|
||||
host: "vscode.dev" # Must be a TLS 1.2 server
|
||||
# 应当是一个TLS 1.2 服务器
|
||||
password: [YOUR_RESTLS_PASSWORD]
|
||||
version-hint: "tls12"
|
||||
restls-script: "1000?100<1,500~100,350~100,600~100,400~200"
|
||||
|
||||
# vmess
|
||||
# cipher支持 auto/aes-128-gcm/chacha20-poly1305/none
|
||||
- name: "vmess"
|
||||
@ -332,7 +403,7 @@ proxies:
|
||||
# Host: v2ray.com
|
||||
# max-early-data: 2048
|
||||
# early-data-header-name: Sec-WebSocket-Protocol
|
||||
|
||||
|
||||
- name: "vmess-h2"
|
||||
type: vmess
|
||||
server: server
|
||||
@ -348,7 +419,7 @@ proxies:
|
||||
- http.example.com
|
||||
- http-alt.example.com
|
||||
path: /
|
||||
|
||||
|
||||
- name: "vmess-http"
|
||||
type: vmess
|
||||
server: server
|
||||
@ -359,15 +430,15 @@ proxies:
|
||||
# udp: true
|
||||
# network: http
|
||||
# http-opts:
|
||||
# # method: "GET"
|
||||
# # path:
|
||||
# # - '/'
|
||||
# # - '/video'
|
||||
# # headers:
|
||||
# # Connection:
|
||||
# # - keep-alive
|
||||
# method: "GET"
|
||||
# path:
|
||||
# - '/'
|
||||
# - '/video'
|
||||
# headers:
|
||||
# Connection:
|
||||
# - keep-alive
|
||||
# ip-version: ipv4 # 设置使用 IP 类型偏好,可选:ipv4,ipv6,dual,默认值:dual
|
||||
|
||||
|
||||
- name: vmess-grpc
|
||||
server: server
|
||||
port: 443
|
||||
@ -383,100 +454,7 @@ proxies:
|
||||
grpc-opts:
|
||||
grpc-service-name: "example"
|
||||
# ip-version: ipv4
|
||||
|
||||
# socks5
|
||||
- name: "socks"
|
||||
type: socks5
|
||||
server: server
|
||||
port: 443
|
||||
# username: username
|
||||
# password: password
|
||||
# tls: true
|
||||
# fingerprint: xxxx
|
||||
# skip-cert-verify: true
|
||||
# udp: true
|
||||
# ip-version: ipv6
|
||||
|
||||
# http
|
||||
- name: "http"
|
||||
type: http
|
||||
server: server
|
||||
port: 443
|
||||
# username: username
|
||||
# password: password
|
||||
# tls: true # https
|
||||
# skip-cert-verify: true
|
||||
# sni: custom.com
|
||||
# fingerprint: xxxx # 同 experimental.fingerprints 使用 sha256 指纹,配置协议独立的指纹,将忽略 experimental.fingerprints
|
||||
# ip-version: dual
|
||||
|
||||
# Snell
|
||||
# Beware that there's currently no UDP support yet
|
||||
- name: "snell"
|
||||
type: snell
|
||||
server: server
|
||||
port: 44046
|
||||
psk: yourpsk
|
||||
# version: 2
|
||||
# obfs-opts:
|
||||
# mode: http # or tls
|
||||
# host: bing.com
|
||||
|
||||
# Trojan
|
||||
- name: "trojan"
|
||||
type: trojan
|
||||
server: server
|
||||
port: 443
|
||||
password: yourpsk
|
||||
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
|
||||
# fingerprint: xxxx
|
||||
# udp: true
|
||||
# sni: example.com # aka server name
|
||||
# alpn:
|
||||
# - h2
|
||||
# - http/1.1
|
||||
# skip-cert-verify: true
|
||||
|
||||
- name: trojan-grpc
|
||||
server: server
|
||||
port: 443
|
||||
type: trojan
|
||||
password: "example"
|
||||
network: grpc
|
||||
sni: example.com
|
||||
# skip-cert-verify: true
|
||||
# fingerprint: xxxx
|
||||
udp: true
|
||||
grpc-opts:
|
||||
grpc-service-name: "example"
|
||||
|
||||
- name: trojan-ws
|
||||
server: server
|
||||
port: 443
|
||||
type: trojan
|
||||
password: "example"
|
||||
network: ws
|
||||
sni: example.com
|
||||
# skip-cert-verify: true
|
||||
# fingerprint: xxxx
|
||||
udp: true
|
||||
# ws-opts:
|
||||
# path: /path
|
||||
# headers:
|
||||
# Host: example.com
|
||||
|
||||
- name: "trojan-xtls"
|
||||
type: trojan
|
||||
server: server
|
||||
port: 443
|
||||
password: yourpsk
|
||||
flow: "xtls-rprx-direct" # xtls-rprx-origin xtls-rprx-direct
|
||||
flow-show: true
|
||||
# udp: true
|
||||
# sni: example.com # aka server name
|
||||
# skip-cert-verify: true
|
||||
# fingerprint: xxxx
|
||||
|
||||
|
||||
# vless
|
||||
- name: "vless-tcp"
|
||||
type: vless
|
||||
@ -489,7 +467,53 @@ proxies:
|
||||
# skip-cert-verify: true
|
||||
# fingerprint: xxxx
|
||||
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
|
||||
|
||||
|
||||
- name: "vless-vision"
|
||||
type: vless
|
||||
server: server
|
||||
port: 443
|
||||
uuid: uuid
|
||||
network: tcp
|
||||
tls: true
|
||||
udp: true
|
||||
flow: xtls-rprx-vision
|
||||
client-fingerprint: chrome
|
||||
# fingerprint: xxxx
|
||||
# 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"
|
||||
type: vless
|
||||
server: server
|
||||
@ -506,7 +530,62 @@ proxies:
|
||||
path: "/"
|
||||
headers:
|
||||
Host: example.com
|
||||
|
||||
|
||||
# Trojan
|
||||
- name: "trojan"
|
||||
type: trojan
|
||||
server: server
|
||||
port: 443
|
||||
password: yourpsk
|
||||
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
|
||||
# fingerprint: xxxx
|
||||
# udp: true
|
||||
# sni: example.com # aka server name
|
||||
# alpn:
|
||||
# - h2
|
||||
# - http/1.1
|
||||
# skip-cert-verify: true
|
||||
|
||||
- name: trojan-grpc
|
||||
server: server
|
||||
port: 443
|
||||
type: trojan
|
||||
password: "example"
|
||||
network: grpc
|
||||
sni: example.com
|
||||
# skip-cert-verify: true
|
||||
# fingerprint: xxxx
|
||||
udp: true
|
||||
grpc-opts:
|
||||
grpc-service-name: "example"
|
||||
|
||||
- name: trojan-ws
|
||||
server: server
|
||||
port: 443
|
||||
type: trojan
|
||||
password: "example"
|
||||
network: ws
|
||||
sni: example.com
|
||||
# skip-cert-verify: true
|
||||
# fingerprint: xxxx
|
||||
udp: true
|
||||
# ws-opts:
|
||||
# path: /path
|
||||
# headers:
|
||||
# Host: example.com
|
||||
|
||||
- name: "trojan-xtls"
|
||||
type: trojan
|
||||
server: server
|
||||
port: 443
|
||||
password: yourpsk
|
||||
flow: "xtls-rprx-direct" # xtls-rprx-origin xtls-rprx-direct
|
||||
flow-show: true
|
||||
# udp: true
|
||||
# sni: example.com # aka server name
|
||||
# skip-cert-verify: true
|
||||
# fingerprint: xxxx
|
||||
|
||||
#hysteria
|
||||
- name: "hysteria"
|
||||
type: hysteria
|
||||
@ -532,7 +611,8 @@ proxies:
|
||||
# disable_mtu_discovery: false
|
||||
# fingerprint: xxxx
|
||||
# fast-open: true # 支持 TCP 快速打开,默认为 false
|
||||
|
||||
|
||||
# wireguard
|
||||
- name: "wg"
|
||||
type: wireguard
|
||||
server: 162.159.192.1
|
||||
@ -542,7 +622,11 @@ proxies:
|
||||
private-key: eCtXsJZ27+4PbhDkHnB923tkUn2Gj59wZw5wFA75MnU=
|
||||
public-key: Cr8hWlKvtDt7nrvf+f0brNQQzabAqrjfBvas9pmowjo=
|
||||
udp: true
|
||||
# reserved: 'U4An'
|
||||
reserved: "U4An"
|
||||
# 数组格式也是合法的
|
||||
# reserved: [209,98,59]
|
||||
|
||||
# tuic
|
||||
- name: tuic
|
||||
server: www.example.com
|
||||
port: 10443
|
||||
@ -551,16 +635,17 @@ proxies:
|
||||
# ip: 127.0.0.1 # for overwriting the DNS lookup result of the server address set in option 'server'
|
||||
# heartbeat-interval: 10000
|
||||
# alpn: [h3]
|
||||
# disable-sni: true
|
||||
disable-sni: true
|
||||
reduce-rtt: true
|
||||
# request-timeout: 8000
|
||||
request-timeout: 8000
|
||||
udp-relay-mode: native # Available: "native", "quic". Default: "native"
|
||||
# congestion-controller: bbr # Available: "cubic", "new_reno", "bbr". Default: "cubic"
|
||||
# max-udp-relay-packet-size: 1500
|
||||
# fast-open: true
|
||||
# skip-cert-verify: true
|
||||
# max-open-streams: 20 # default 100, too many open streams may hurt performance
|
||||
|
||||
# sni: example.com
|
||||
|
||||
# ShadowsocksR
|
||||
# The supported ciphers (encryption methods): all stream ciphers in ss
|
||||
# The supported obfses:
|
||||
@ -581,8 +666,7 @@ proxies:
|
||||
# protocol-param: "#"
|
||||
# udp: true
|
||||
|
||||
proxy-groups:
|
||||
# 代理链,若落地协议支持 UDP over TCP 则可支持 UDP
|
||||
proxy-groups: # 代理链,若落地协议支持 UDP over TCP 则可支持 UDP
|
||||
# Traffic: clash <-> http <-> vmess <-> ss1 <-> ss2 <-> Internet
|
||||
- name: "relay"
|
||||
type: relay
|
||||
@ -591,7 +675,7 @@ proxy-groups:
|
||||
- vmess
|
||||
- ss1
|
||||
- ss2
|
||||
|
||||
|
||||
# url-test 将按照 url 测试结果使用延迟最低节点
|
||||
- name: "auto"
|
||||
type: url-test
|
||||
@ -603,7 +687,7 @@ proxy-groups:
|
||||
# lazy: true
|
||||
url: "https://cp.cloudflare.com/generate_204"
|
||||
interval: 300
|
||||
|
||||
|
||||
# fallback 将按照 url 测试结果按照节点顺序选择
|
||||
- name: "fallback-auto"
|
||||
type: fallback
|
||||
@ -613,7 +697,7 @@ proxy-groups:
|
||||
- vmess1
|
||||
url: "https://cp.cloudflare.com/generate_204"
|
||||
interval: 300
|
||||
|
||||
|
||||
# load-balance 将按照算法随机选择节点
|
||||
- name: "load-balance"
|
||||
type: load-balance
|
||||
@ -623,8 +707,8 @@ proxy-groups:
|
||||
- vmess1
|
||||
url: "https://cp.cloudflare.com/generate_204"
|
||||
interval: 300
|
||||
# strategy: consistent-hashing # 可选 round-robin 和 sticky-sessions
|
||||
|
||||
# strategy: consistent-hashing # 可选 round-robin 和 sticky-sessions
|
||||
|
||||
# select 用户自行选择节点
|
||||
- name: Proxy
|
||||
type: select
|
||||
@ -634,7 +718,7 @@ proxy-groups:
|
||||
- ss2
|
||||
- vmess1
|
||||
- auto
|
||||
|
||||
|
||||
# 配置指定 interface-name 和 fwmark 的 DIRECT
|
||||
- name: en1
|
||||
type: select
|
||||
@ -642,7 +726,7 @@ proxy-groups:
|
||||
routing-mark: 6667
|
||||
proxies:
|
||||
- DIRECT
|
||||
|
||||
|
||||
- name: UseProvider
|
||||
type: select
|
||||
filter: "HK|TW" # 正则表达式,过滤 provider1 中节点名包含 HK 或 TW
|
||||
@ -689,7 +773,8 @@ rules:
|
||||
- DOMAIN-KEYWORD,google,ss1
|
||||
- IP-CIDR,1.1.1.1/32,ss1
|
||||
- IP-CIDR6,2409::/64,DIRECT
|
||||
- SUB-RULE,(OR,((NETWORK,TCP),(NETWORK,UDP))),sub-rule-name1 # 当满足条件是 TCP 或 UDP 流量时,使用名为 sub-rule-name1 当规则集
|
||||
# 当满足条件是 TCP 或 UDP 流量时,使用名为 sub-rule-name1 的规则集
|
||||
- SUB-RULE,(OR,((NETWORK,TCP),(NETWORK,UDP))),sub-rule-name1
|
||||
- SUB-RULE,(AND,((NETWORK,UDP))),sub-rule-name2
|
||||
# 定义多个子规则集,规则将以分叉匹配,使用 SUB-RULE 使用
|
||||
# google.com(not match)--> baidu.com(match)
|
||||
@ -716,15 +801,6 @@ sub-rules:
|
||||
- IP-CIDR,8.8.8.8/32,ss1
|
||||
- DOMAIN,dns.alidns.com,REJECT
|
||||
|
||||
tls:
|
||||
certificate: string # 证书 PEM 格式,或者 证书的路径
|
||||
private-key: string # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
# 自定义证书验证,将加入 Clash 证书验证中,绝大多数 TLS 相关支持,如:DNS
|
||||
# 可用于自定义证书的验证
|
||||
custom-certificates:
|
||||
- certificate: string # 证书 PEM 格式,或者 证书的路径
|
||||
private-key: string # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
|
||||
# 流量入站
|
||||
listeners:
|
||||
- name: socks5-in-1
|
||||
@ -734,14 +810,14 @@ listeners:
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理
|
||||
# udp: false # 默认 true
|
||||
|
||||
|
||||
- name: http-in-1
|
||||
type: http
|
||||
port: 10809
|
||||
listen: 0.0.0.0
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
|
||||
|
||||
- name: mixed-in-1
|
||||
type: mixed # HTTP(S) 和 SOCKS 代理混合
|
||||
port: 10810
|
||||
@ -749,14 +825,14 @@ listeners:
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
# udp: false # 默认 true
|
||||
|
||||
|
||||
- name: reidr-in-1
|
||||
type: redir
|
||||
port: 10811
|
||||
listen: 0.0.0.0
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
|
||||
|
||||
- name: tproxy-in-1
|
||||
type: tproxy
|
||||
port: 10812
|
||||
@ -764,7 +840,7 @@ listeners:
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
# udp: false # 默认 true
|
||||
|
||||
|
||||
- name: shadowsocks-in-1
|
||||
type: shadowsocks
|
||||
port: 10813
|
||||
@ -773,7 +849,7 @@ listeners:
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
password: vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg=
|
||||
cipher: 2022-blake3-aes-256-gcm
|
||||
|
||||
|
||||
- name: vmess-in-1
|
||||
type: vmess
|
||||
port: 10814
|
||||
@ -784,7 +860,7 @@ listeners:
|
||||
- username: 1
|
||||
uuid: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68
|
||||
alterId: 1
|
||||
|
||||
|
||||
- name: tuic-in-1
|
||||
type: tuic
|
||||
port: 10815
|
||||
@ -801,7 +877,7 @@ listeners:
|
||||
# alpn:
|
||||
# - h3
|
||||
# max-udp-relay-packet-size: 1500
|
||||
|
||||
|
||||
- name: tunnel-in-1
|
||||
type: tunnel
|
||||
port: 10816
|
||||
@ -810,7 +886,7 @@ listeners:
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
network: [tcp, udp]
|
||||
target: target.com
|
||||
|
||||
|
||||
- name: tun-in-1
|
||||
type: tun
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
@ -826,25 +902,25 @@ listeners:
|
||||
inet6-address: # 必须手动设置ipv6地址段
|
||||
- "fdfe:dcba:9877::1/126"
|
||||
# strict_route: true # 将所有连接路由到tun来防止泄漏,但你的设备将无法其他设备被访问
|
||||
# inet4_route_address: # 启用 auto_route 时使用自定义路由而不是默认路由
|
||||
# - 0.0.0.0/1
|
||||
# - 128.0.0.0/1
|
||||
# inet6_route_address: # 启用 auto_route 时使用自定义路由而不是默认路由
|
||||
# - "::/1"
|
||||
# - "8000::/1"
|
||||
# inet4_route_address: # 启用 auto_route 时使用自定义路由而不是默认路由
|
||||
# - 0.0.0.0/1
|
||||
# - 128.0.0.0/1
|
||||
# inet6_route_address: # 启用 auto_route 时使用自定义路由而不是默认路由
|
||||
# - "::/1"
|
||||
# - "8000::/1"
|
||||
# endpoint_independent_nat: false # 启用独立于端点的 NAT
|
||||
# include_uid: # UID 规则仅在 Linux 下被支持,并且需要 auto_route
|
||||
# - 0
|
||||
# include_uid_range: # 限制被路由的的用户范围
|
||||
# - 1000-99999
|
||||
# exclude_uid: # 排除路由的的用户
|
||||
#- 1000
|
||||
# - 1000
|
||||
# exclude_uid_range: # 排除路由的的用户范围
|
||||
# - 1000-99999
|
||||
|
||||
|
||||
# Android 用户和应用规则仅在 Android 下被支持
|
||||
# 并且需要 auto_route
|
||||
|
||||
|
||||
# include_android_user: # 限制被路由的 Android 用户
|
||||
# - 0
|
||||
# - 10
|
||||
@ -852,3 +928,23 @@ listeners:
|
||||
# - com.android.chrome
|
||||
# exclude_package: # 排除被路由的 Android 应用包名
|
||||
# - com.android.captiveportallogin
|
||||
|
||||
# 入口配置与 Listener 等价,传入流量将和 socks,mixed 等入口一样按照 mode 所指定的方式进行匹配处理
|
||||
# shadowsocks,vmess 入口配置(传入流量将和socks,mixed等入口一样按照mode所指定的方式进行匹配处理)
|
||||
# ss-config: ss://2022-blake3-aes-256-gcm:vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg=@:23456
|
||||
# vmess-config: vmess://1:9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68@:12345
|
||||
|
||||
# tuic服务器入口(传入流量将和socks,mixed等入口一样按照mode所指定的方式进行匹配处理)
|
||||
# tuic-server:
|
||||
# enable: true
|
||||
# listen: 127.0.0.1:10443
|
||||
# token:
|
||||
# - TOKEN
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
# congestion-controller: bbr
|
||||
# max-idle-time: 15000
|
||||
# authentication-timeout: 1000
|
||||
# alpn:
|
||||
# - h3
|
||||
# max-udp-relay-packet-size: 1500
|
||||
|
47
go.mod
47
go.mod
@ -10,7 +10,7 @@ require (
|
||||
github.com/go-chi/chi/v5 v5.0.8
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-chi/render v1.0.2
|
||||
github.com/gofrs/uuid v4.3.1+incompatible
|
||||
github.com/gofrs/uuid v4.4.0+incompatible
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/hashicorp/golang-lru v0.5.4
|
||||
@ -18,39 +18,42 @@ require (
|
||||
github.com/jpillora/backoff v1.0.0
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
||||
github.com/mdlayher/netlink v1.7.2-0.20221213171556-9881fafed8c7
|
||||
github.com/metacubex/quic-go v0.32.0
|
||||
github.com/metacubex/sing-shadowsocks v0.1.1-0.20230202072246-e2bef5f088c7
|
||||
github.com/metacubex/sing-tun v0.1.1-0.20230213124625-28d27a0c236b
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230213124601-d04406a109b4
|
||||
github.com/metacubex/quic-go v0.33.1
|
||||
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-wireguard v0.0.0-20230310035749-f7595fcae5cb
|
||||
github.com/miekg/dns v1.1.50
|
||||
github.com/mroth/weightedrand/v2 v2.0.0
|
||||
github.com/oschwald/geoip2-golang v1.8.0
|
||||
github.com/refraction-networking/utls v1.2.0
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97
|
||||
github.com/sagernet/sing v0.1.7-0.20230207063819-27d2950cdbe9
|
||||
github.com/sagernet/sing-vmess v0.1.1-0.20230212211128-cb4e47dd0acb
|
||||
github.com/sagernet/sing v0.1.8
|
||||
github.com/sagernet/sing-shadowtls v0.1.0
|
||||
github.com/sagernet/sing-vmess v0.1.3-0.20230307060529-d110e81a50bc
|
||||
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d
|
||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2
|
||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c
|
||||
github.com/samber/lo v1.37.0
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837
|
||||
github.com/zhangyunhao116/fastrand v0.3.0
|
||||
go.etcd.io/bbolt v1.3.6
|
||||
go.uber.org/atomic v1.10.0
|
||||
go.uber.org/automaxprocs v1.5.1
|
||||
golang.org/x/crypto v0.5.0
|
||||
golang.org/x/crypto v0.6.0
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db
|
||||
golang.org/x/net v0.5.0
|
||||
golang.org/x/net v0.7.0
|
||||
golang.org/x/sync v0.1.0
|
||||
golang.org/x/sys v0.4.0
|
||||
google.golang.org/protobuf v1.28.1
|
||||
golang.org/x/sys v0.5.0
|
||||
google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
lukechampine.com/blake3 v1.1.7
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/3andne/restls-client-go v0.1.4
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||
@ -59,25 +62,23 @@ require (
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.15.12 // indirect
|
||||
github.com/klauspost/compress v1.15.15 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
|
||||
github.com/mdlayher/socket v0.4.0 // indirect
|
||||
github.com/metacubex/gvisor v0.0.0-20230213124051-7a16c835d80e // indirect
|
||||
github.com/metacubex/gvisor v0.0.0-20230304153416-e2bb9c726005 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.10.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.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.0 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.1.0 // indirect
|
||||
github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e // indirect
|
||||
github.com/quic-go/qtls-go1-19 v0.2.1 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.1.1 // 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/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||
golang.org/x/mod v0.6.0 // indirect
|
||||
golang.org/x/text v0.6.0 // indirect
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
|
||||
golang.org/x/tools v0.2.0 // indirect
|
||||
golang.org/x/mod v0.7.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
|
||||
golang.org/x/tools v0.5.0 // indirect
|
||||
)
|
||||
|
||||
replace go.uber.org/atomic v1.10.0 => github.com/metacubex/uber-atomic v0.0.0-20230202125923-feb10b770370
|
||||
|
94
go.sum
94
go.sum
@ -1,9 +1,11 @@
|
||||
github.com/3andne/restls-client-go v0.1.4 h1:kLNC2aSRHPlEVYmTj6EOqJoorCpobEe2toMRSfBF7FU=
|
||||
github.com/3andne/restls-client-go v0.1.4/go.mod h1:04CGbRk1BwBiEDles8b5mlKgTqIwE5MqF7JDloJV47I=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
@ -28,8 +30,8 @@ github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg=
|
||||
github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/gofrs/uuid v4.3.1+incompatible h1:0/KbAdpx3UXAx1kEOWHJeOkpbgRFGHVgv+CFIY7dBJI=
|
||||
github.com/gofrs/uuid v4.3.1+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
@ -67,8 +69,8 @@ github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGu
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM=
|
||||
github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
|
||||
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
|
||||
github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
@ -87,16 +89,16 @@ github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZ
|
||||
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||
github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw=
|
||||
github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc=
|
||||
github.com/metacubex/gvisor v0.0.0-20230213124051-7a16c835d80e h1:j4j2dlV2d//FAsQlRUriH6nvv36AEAhECbNy7narf1M=
|
||||
github.com/metacubex/gvisor v0.0.0-20230213124051-7a16c835d80e/go.mod h1:abc7OdNmWlhcNHz84ECEosd5ND5pnWQmD8W55p/4cuc=
|
||||
github.com/metacubex/quic-go v0.32.0 h1:dSD8LB4MSeBuD4otd8y1DUZcRdDcEB0Ax5esPOqn2Hw=
|
||||
github.com/metacubex/quic-go v0.32.0/go.mod h1:yParIzDYUd/t/pzFlDtZKhnvSqbUu0bPChlKEGmJStA=
|
||||
github.com/metacubex/sing-shadowsocks v0.1.1-0.20230202072246-e2bef5f088c7 h1:MNCGIpXhxXn9ck5bxfm/cW9Nr2FGQ5cakcGK0yKZcak=
|
||||
github.com/metacubex/sing-shadowsocks v0.1.1-0.20230202072246-e2bef5f088c7/go.mod h1:8pBSYDKVxTtqUtGZyEh4ZpFJXwP6wBVVKrs6oQiOwmQ=
|
||||
github.com/metacubex/sing-tun v0.1.1-0.20230213124625-28d27a0c236b h1:ZF/oNrSCaxIFoZmFQCiUx67t9aENZjyuqw2n4zw3L2o=
|
||||
github.com/metacubex/sing-tun v0.1.1-0.20230213124625-28d27a0c236b/go.mod h1:TjuaYuR/g1MaY3um89xTfRNt61FJ2IcI/m5zD8QBxw4=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230213124601-d04406a109b4 h1:d96mCF/LYyC9kULd2xwcXfP0Jd8klrOngmRxuUIZg/8=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230213124601-d04406a109b4/go.mod h1:p2VpJuxRefgVMxc8cmatMGSFNvYbjMYMsXJOe7qFstw=
|
||||
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/quic-go v0.33.1 h1:ZIxZFGivpSLOEZuuNkLy+aPvo1RP4uRBjNg3SAkXwIg=
|
||||
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/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/go.mod h1:b/19bRRhwampNPV+1gVDyDsQHmuGDaplxPQkwJh1kj4=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230310035749-f7595fcae5cb h1:uhvzbtOvyg2c1k1H2EeVPuPvTEjDHCq4+U0AljG40P8=
|
||||
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/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||
@ -115,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/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/qtls-go1-18 v0.2.0 h1:5ViXqBZ90wpUcZS0ge79rf029yx0dYB0McyPJwqqj7U=
|
||||
github.com/quic-go/qtls-go1-18 v0.2.0/go.mod h1:moGulGHK7o6O8lSPSZNoOwcLvJKJ85vVNc7oJFD65bc=
|
||||
github.com/quic-go/qtls-go1-19 v0.2.0 h1:Cvn2WdhyViFUHoOqK52i51k4nDX8EwIh5VJiVM4nttk=
|
||||
github.com/quic-go/qtls-go1-19 v0.2.0/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
||||
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/refraction-networking/utls v1.2.0 h1:U5f8wkij2NVinfLuJdFP3gCMwIHs+EzvhxmYdXgiapo=
|
||||
github.com/refraction-networking/utls v1.2.0/go.mod h1:NPq+cVqzH7D1BeOkmOcb5O/8iVewAsiVt2x1/eO0hgQ=
|
||||
github.com/quic-go/qtls-go1-19 v0.2.1 h1:aJcKNMkH5ASEJB9FXNeZCyTEIHU1J7MmHyz1Q1TSG1A=
|
||||
github.com/quic-go/qtls-go1-19 v0.2.1/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
||||
github.com/quic-go/qtls-go1-20 v0.1.1 h1:KbChDlg82d3IHqaj2bn6GfKRj84Per2VGf5XV3wSwQk=
|
||||
github.com/quic-go/qtls-go1-20 v0.1.1/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e h1:5CFRo8FJbCuf5s/eTBdZpmMbn8Fe2eSMLNAYfKanA34=
|
||||
github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e/go.mod h1:qbt0dWObotCfcjAJJ9AxtFPNSDUfZF+6dCpgKEOBn/g=
|
||||
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/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/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||
github.com/sagernet/sing v0.1.7-0.20230207063819-27d2950cdbe9 h1:qnXh4RjHsNjdZXkfbqwVqAzYUfc160gfkS5gepmsA+A=
|
||||
github.com/sagernet/sing v0.1.7-0.20230207063819-27d2950cdbe9/go.mod h1:JLSXsPTGRJFo/3X7EcAOCUgJH2/gAoxSJgBsnCZRp/w=
|
||||
github.com/sagernet/sing-vmess v0.1.1-0.20230212211128-cb4e47dd0acb h1:oyd3w17fXNmWVYFUe17YVHJW5CLW9X2mxJFDP/IWrAM=
|
||||
github.com/sagernet/sing-vmess v0.1.1-0.20230212211128-cb4e47dd0acb/go.mod h1:9KkmnQzTL4Gvv8U2TRAH2BOITCGsGPpHtUPP5sxn5sY=
|
||||
github.com/sagernet/sing v0.1.8 h1:6DKo2FkSHn0nUcjO7bAext/ai7y7pCusK/+fScBJ5Jk=
|
||||
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/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc=
|
||||
github.com/sagernet/sing-vmess v0.1.3-0.20230307060529-d110e81a50bc h1:vqlYWupvVDRpvv2F+RtECJN+VbuKjLtmQculQvOecls=
|
||||
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/go.mod h1:jk6Ii8Y3En+j2KQDLgdgQGwb3M6y7EL567jFnGYhN9g=
|
||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 h1:kDUqhc9Vsk5HJuhfIATJ8oQwBmpOZJuozQG7Vk88lL4=
|
||||
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/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI=
|
||||
github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw=
|
||||
@ -162,6 +162,8 @@ github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1
|
||||
github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837 h1:AHhUwwFJGl27E46OpdJHplZkK09m7aETNBNzhT6t15M=
|
||||
github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837/go.mod h1:YJTRELIWrGxR1s8xcEBgxcxBfwQfMGjdvNLTjN9XFgY=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/zhangyunhao116/fastrand v0.3.0 h1:7bwe124xcckPulX6fxtr2lFdO2KQqaefdtbk+mqO/Ig=
|
||||
github.com/zhangyunhao116/fastrand v0.3.0/go.mod h1:0v5KgHho0VE6HU192HnY15de/oDS8UrbBChIFjIhBtc=
|
||||
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
||||
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
||||
go.uber.org/automaxprocs v1.5.1 h1:e1YG66Lrk73dn4qhg8WFSvhF0JuFQF0ERIp4rpuV8Qk=
|
||||
@ -169,15 +171,15 @@ go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
|
||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
||||
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
|
||||
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
|
||||
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
|
||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
@ -190,8 +192,8 @@ golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
|
||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
@ -220,31 +222,31 @@ golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
|
||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
|
||||
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
|
||||
golang.org/x/tools v0.5.0 h1:+bSpV5HIeWkuvgaMfI3UmKRThoTA5ODJTUd8T17NO+4=
|
||||
golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d h1:qp0AnQCvRCMlu9jBjtdbTaaEmThIgZOrbVyDEOcmKhQ=
|
||||
google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
@ -81,13 +81,13 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
||||
updateRules(cfg.Rules, cfg.SubRules, cfg.RuleProviders)
|
||||
updateSniffer(cfg.Sniffer)
|
||||
updateHosts(cfg.Hosts)
|
||||
updateGeneral(cfg.General)
|
||||
initInnerTcp()
|
||||
updateDNS(cfg.DNS, cfg.General.IPv6)
|
||||
loadProxyProvider(cfg.Providers)
|
||||
updateProfile(cfg)
|
||||
loadRuleProvider(cfg.RuleProviders)
|
||||
updateGeneral(cfg.General, force)
|
||||
updateListeners(cfg.Listeners)
|
||||
updateListeners(cfg.General, cfg.Listeners, force)
|
||||
updateIPTables(cfg)
|
||||
updateTun(cfg.General)
|
||||
updateExperimental(cfg)
|
||||
@ -128,18 +128,40 @@ func GetGeneral() *config.General {
|
||||
GeodataLoader: G.LoaderName(),
|
||||
Interface: dialer.DefaultInterface.Load(),
|
||||
Sniffing: tunnel.IsSniffing(),
|
||||
TCPConcurrent: dialer.GetDial(),
|
||||
TCPConcurrent: dialer.GetTcpConcurrent(),
|
||||
}
|
||||
|
||||
return general
|
||||
}
|
||||
|
||||
func updateListeners(listeners map[string]C.InboundListener) {
|
||||
func updateListeners(general *config.General, listeners map[string]C.InboundListener, force bool) {
|
||||
tcpIn := tunnel.TCPIn()
|
||||
udpIn := tunnel.UDPIn()
|
||||
natTable := tunnel.NatTable()
|
||||
|
||||
listener.PatchInboundListeners(listeners, tcpIn, udpIn, natTable, true)
|
||||
if !force {
|
||||
return
|
||||
}
|
||||
|
||||
if general.Interface == "" && (!general.Tun.Enable || !general.Tun.AutoDetectInterface) {
|
||||
dialer.DefaultInterface.Store(general.Interface)
|
||||
}
|
||||
|
||||
allowLan := general.AllowLan
|
||||
listener.SetAllowLan(allowLan)
|
||||
|
||||
bindAddress := general.BindAddress
|
||||
listener.SetBindAddress(bindAddress)
|
||||
listener.ReCreateHTTP(general.Port, tcpIn)
|
||||
listener.ReCreateSocks(general.SocksPort, tcpIn, udpIn)
|
||||
listener.ReCreateRedir(general.RedirPort, tcpIn, udpIn, natTable)
|
||||
listener.ReCreateAutoRedir(general.EBpf.AutoRedir, tcpIn, udpIn)
|
||||
listener.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn, natTable)
|
||||
listener.ReCreateMixed(general.MixedPort, tcpIn, udpIn)
|
||||
listener.ReCreateShadowSocks(general.ShadowSocksConfig, tcpIn, udpIn)
|
||||
listener.ReCreateVmess(general.VmessConfig, tcpIn, udpIn)
|
||||
listener.ReCreateTuic(LC.TuicServer(general.TuicServer), tcpIn, udpIn)
|
||||
}
|
||||
|
||||
func updateExperimental(c *config.Config) {
|
||||
@ -147,9 +169,11 @@ func updateExperimental(c *config.Config) {
|
||||
}
|
||||
|
||||
func preUpdateExperimental(c *config.Config) {
|
||||
CTLS.AddCertificate(c.TLS.PrivateKey, c.TLS.Certificate)
|
||||
CTLS.ResetCertificate()
|
||||
for _, c := range c.TLS.CustomTrustCert {
|
||||
CTLS.AddCertificate(c.PrivateKey, c.Certificate)
|
||||
if err := CTLS.AddCertificate(c); err != nil {
|
||||
log.Warnln("%s\nadd error: %s", c, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -166,6 +190,7 @@ func updateDNS(c *config.DNS, generalIPv6 bool) {
|
||||
Main: c.NameServer,
|
||||
Fallback: c.Fallback,
|
||||
IPv6: c.IPv6 && generalIPv6,
|
||||
IPv6Timeout: c.IPv6Timeout,
|
||||
EnhancedMode: c.EnhancedMode,
|
||||
Pool: c.FakeIPRange,
|
||||
Hosts: c.Hosts,
|
||||
@ -201,8 +226,8 @@ func updateDNS(c *config.DNS, generalIPv6 bool) {
|
||||
dns.ReCreateServer(c.Listen, r, m)
|
||||
}
|
||||
|
||||
func updateHosts(tree *trie.DomainTrie[netip.Addr]) {
|
||||
resolver.DefaultHosts = tree
|
||||
func updateHosts(tree *trie.DomainTrie[resolver.HostValue]) {
|
||||
resolver.DefaultHosts = resolver.NewHosts(tree)
|
||||
}
|
||||
|
||||
func updateProxies(proxies map[string]C.Proxy, providers map[string]provider.ProxyProvider) {
|
||||
@ -304,25 +329,28 @@ func updateTunnels(tunnels []LC.Tunnel) {
|
||||
listener.PatchTunnel(tunnels, tunnel.TCPIn(), tunnel.UDPIn())
|
||||
}
|
||||
|
||||
func updateGeneral(general *config.General, force bool) {
|
||||
func updateGeneral(general *config.General) {
|
||||
tunnel.SetMode(general.Mode)
|
||||
tunnel.SetFindProcessMode(general.FindProcessMode)
|
||||
dialer.DisableIPv6 = !general.IPv6
|
||||
if !dialer.DisableIPv6 {
|
||||
log.Infoln("Use IPv6")
|
||||
}
|
||||
resolver.DisableIPv6 = dialer.DisableIPv6
|
||||
resolver.DisableIPv6 = !general.IPv6
|
||||
|
||||
if general.TCPConcurrent {
|
||||
dialer.SetDial(general.TCPConcurrent)
|
||||
dialer.SetTcpConcurrent(general.TCPConcurrent)
|
||||
log.Infoln("Use tcp concurrent")
|
||||
}
|
||||
|
||||
adapter.UnifiedDelay.Store(general.UnifiedDelay)
|
||||
dialer.DefaultInterface.Store(general.Interface)
|
||||
inbound.SetTfo(general.InboundTfo)
|
||||
|
||||
if dialer.DefaultInterface.Load() != "" {
|
||||
log.Infoln("Use interface name: %s", general.Interface)
|
||||
adapter.UnifiedDelay.Store(general.UnifiedDelay)
|
||||
// Avoid reload configuration clean the value, causing traffic loops
|
||||
if listener.GetTunConf().Enable && listener.GetTunConf().AutoDetectInterface {
|
||||
// changed only when the name is specified
|
||||
// if name is empty, setting delay until after tun loaded
|
||||
if general.Interface != "" && (!general.Tun.Enable || !general.Tun.AutoDetectInterface) {
|
||||
dialer.DefaultInterface.Store(general.Interface)
|
||||
}
|
||||
} else {
|
||||
dialer.DefaultInterface.Store(general.Interface)
|
||||
}
|
||||
|
||||
dialer.DefaultRoutingMark.Store(int32(general.RoutingMark))
|
||||
@ -331,35 +359,8 @@ func updateGeneral(general *config.General, force bool) {
|
||||
}
|
||||
|
||||
iface.FlushCache()
|
||||
|
||||
if !force {
|
||||
return
|
||||
}
|
||||
|
||||
geodataLoader := general.GeodataLoader
|
||||
G.SetLoader(geodataLoader)
|
||||
|
||||
allowLan := general.AllowLan
|
||||
listener.SetAllowLan(allowLan)
|
||||
|
||||
bindAddress := general.BindAddress
|
||||
listener.SetBindAddress(bindAddress)
|
||||
|
||||
inbound.SetTfo(general.InboundTfo)
|
||||
|
||||
tcpIn := tunnel.TCPIn()
|
||||
udpIn := tunnel.UDPIn()
|
||||
natTable := tunnel.NatTable()
|
||||
|
||||
listener.ReCreateHTTP(general.Port, tcpIn)
|
||||
listener.ReCreateSocks(general.SocksPort, tcpIn, udpIn)
|
||||
listener.ReCreateRedir(general.RedirPort, tcpIn, udpIn, natTable)
|
||||
listener.ReCreateAutoRedir(general.EBpf.AutoRedir, tcpIn, udpIn)
|
||||
listener.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn, natTable)
|
||||
listener.ReCreateMixed(general.MixedPort, tcpIn, udpIn)
|
||||
listener.ReCreateShadowSocks(general.ShadowSocksConfig, tcpIn, udpIn)
|
||||
listener.ReCreateVmess(general.VmessConfig, tcpIn, udpIn)
|
||||
listener.ReCreateTuic(LC.TuicServer(general.TuicServer), tcpIn, udpIn)
|
||||
}
|
||||
|
||||
func updateUsers(users []auth.AuthUser) {
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"github.com/Dreamacro/clash/config"
|
||||
"github.com/Dreamacro/clash/hub/executor"
|
||||
"github.com/Dreamacro/clash/hub/route"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
type Option func(*config.Config)
|
||||
@ -43,7 +44,7 @@ func Parse(options ...Option) error {
|
||||
|
||||
if cfg.General.ExternalController != "" {
|
||||
go route.Start(cfg.General.ExternalController, cfg.General.ExternalControllerTLS,
|
||||
cfg.General.Secret, cfg.TLS.Certificate, cfg.TLS.PrivateKey)
|
||||
cfg.General.Secret, cfg.TLS.Certificate, cfg.TLS.PrivateKey, cfg.General.LogLevel == log.DEBUG)
|
||||
}
|
||||
|
||||
executor.ApplyConfig(cfg, true)
|
||||
|
@ -228,7 +228,7 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if general.TcpConcurrent != nil {
|
||||
dialer.SetDial(*general.TcpConcurrent)
|
||||
dialer.SetTcpConcurrent(*general.TcpConcurrent)
|
||||
}
|
||||
|
||||
if general.InterfaceName != nil {
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -13,8 +14,8 @@ import (
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/tunnel/statistic"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/gorilla/websocket"
|
||||
@ -43,7 +44,7 @@ func SetUIPath(path string) {
|
||||
}
|
||||
|
||||
func Start(addr string, tlsAddr string, secret string,
|
||||
certificat, privateKey string) {
|
||||
certificat, privateKey string, isDebug bool) {
|
||||
if serverAddr != "" {
|
||||
return
|
||||
}
|
||||
@ -59,6 +60,17 @@ func Start(addr string, tlsAddr string, secret string,
|
||||
MaxAge: 300,
|
||||
})
|
||||
r.Use(corsM.Handler)
|
||||
if isDebug {
|
||||
r.Mount("/debug", func() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Put("/gc", func(w http.ResponseWriter, r *http.Request) {
|
||||
debug.FreeOSMemory()
|
||||
})
|
||||
handler := middleware.Profiler
|
||||
r.Mount("/", handler())
|
||||
return r
|
||||
}())
|
||||
}
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(authentication)
|
||||
r.Get("/", hello)
|
||||
|
@ -1,41 +0,0 @@
|
||||
package sing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
L "github.com/sagernet/sing/common/logger"
|
||||
)
|
||||
|
||||
type logger struct{}
|
||||
|
||||
func (l logger) Trace(args ...any) {
|
||||
log.Debugln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l logger) Debug(args ...any) {
|
||||
log.Debugln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l logger) Info(args ...any) {
|
||||
log.Infoln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l logger) Warn(args ...any) {
|
||||
log.Warnln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l logger) Error(args ...any) {
|
||||
log.Errorln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l logger) Fatal(args ...any) {
|
||||
log.Fatalln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l logger) Panic(args ...any) {
|
||||
log.Fatalln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
var Logger L.Logger = logger{}
|
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
"github.com/Dreamacro/clash/common/sockopt"
|
||||
@ -63,7 +64,7 @@ func New(config LC.ShadowsocksServer, tcpIn chan<- C.ConnContext, udpIn chan<- C
|
||||
case common.Contains(shadowaead.List, config.Cipher):
|
||||
sl.service, err = shadowaead.NewService(config.Cipher, nil, config.Password, udpTimeout, h)
|
||||
case common.Contains(shadowaead_2022.List, config.Cipher):
|
||||
sl.service, err = shadowaead_2022.NewServiceWithPassword(config.Cipher, config.Password, udpTimeout, h)
|
||||
sl.service, err = shadowaead_2022.NewServiceWithPassword(config.Cipher, config.Password, udpTimeout, h, time.Now)
|
||||
default:
|
||||
err = fmt.Errorf("shadowsocks: unsupported method: %s", config.Cipher)
|
||||
return embedSS.New(config, tcpIn, udpIn)
|
||||
|
@ -67,6 +67,26 @@ func CalculateInterfaceName(name string) (tunName string) {
|
||||
return
|
||||
}
|
||||
|
||||
func checkTunName(tunName string) (ok bool) {
|
||||
defer func() {
|
||||
if !ok {
|
||||
log.Warnln("[TUN] Unsupported tunName(%s) in %s, force regenerate by ourselves.", tunName, runtime.GOOS)
|
||||
}
|
||||
}()
|
||||
if runtime.GOOS == "darwin" {
|
||||
if len(tunName) <= 4 {
|
||||
return false
|
||||
}
|
||||
if tunName[:4] != "utun" {
|
||||
return false
|
||||
}
|
||||
if _, parseErr := strconv.ParseInt(tunName[4:], 10, 16); parseErr != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func New(options LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, additions ...inbound.Addition) (l *Listener, err error) {
|
||||
if len(additions) == 0 {
|
||||
additions = []inbound.Addition{
|
||||
@ -75,7 +95,7 @@ func New(options LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapte
|
||||
}
|
||||
}
|
||||
tunName := options.Device
|
||||
if tunName == "" {
|
||||
if tunName == "" || !checkTunName(tunName) {
|
||||
tunName = CalculateInterfaceName(InterfaceName)
|
||||
options.Device = tunName
|
||||
}
|
||||
@ -201,7 +221,7 @@ func New(options LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapte
|
||||
err = E.Cause(err, "build android rules")
|
||||
return
|
||||
}
|
||||
tunIf, err := tunOpen(tunOptions)
|
||||
tunIf, err := tunNew(tunOptions)
|
||||
if err != nil {
|
||||
err = E.Cause(err, "configure tun interface")
|
||||
return
|
||||
@ -217,7 +237,7 @@ func New(options LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapte
|
||||
EndpointIndependentNat: options.EndpointIndependentNat,
|
||||
UDPTimeout: udpTimeout,
|
||||
Handler: handler,
|
||||
Logger: sing.Logger,
|
||||
Logger: log.SingLogger,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -6,6 +6,6 @@ import (
|
||||
tun "github.com/metacubex/sing-tun"
|
||||
)
|
||||
|
||||
func tunOpen(options tun.Options) (tun.Tun, error) {
|
||||
return tun.Open(options)
|
||||
func tunNew(options tun.Options) (tun.Tun, error) {
|
||||
return tun.New(options)
|
||||
}
|
||||
|
@ -8,11 +8,11 @@ import (
|
||||
tun "github.com/metacubex/sing-tun"
|
||||
)
|
||||
|
||||
func tunOpen(options tun.Options) (tunIf tun.Tun, err error) {
|
||||
func tunNew(options tun.Options) (tunIf tun.Tun, err error) {
|
||||
maxRetry := 3
|
||||
for i := 0; i < maxRetry; i++ {
|
||||
timeBegin := time.Now()
|
||||
tunIf, err = tun.Open(options)
|
||||
tunIf, err = tun.New(options)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ func (l *Listener) handleTCP(conn net.Conn, in chan<- C.ConnContext, additions .
|
||||
}
|
||||
|
||||
func New(addr, target, proxy string, in chan<- C.ConnContext, additions ...inbound.Addition) (*Listener, error) {
|
||||
l, err := net.Listen("tcp", addr)
|
||||
l, err := inbound.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
68
log/sing.go
Normal file
68
log/sing.go
Normal file
@ -0,0 +1,68 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
L "github.com/sagernet/sing/common/logger"
|
||||
)
|
||||
|
||||
type singLogger struct{}
|
||||
|
||||
func (l singLogger) TraceContext(ctx context.Context, args ...any) {
|
||||
Debugln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l singLogger) DebugContext(ctx context.Context, args ...any) {
|
||||
Debugln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l singLogger) InfoContext(ctx context.Context, args ...any) {
|
||||
Infoln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l singLogger) WarnContext(ctx context.Context, args ...any) {
|
||||
Warnln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l singLogger) ErrorContext(ctx context.Context, args ...any) {
|
||||
Errorln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l singLogger) FatalContext(ctx context.Context, args ...any) {
|
||||
Fatalln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l singLogger) PanicContext(ctx context.Context, args ...any) {
|
||||
Fatalln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l singLogger) Trace(args ...any) {
|
||||
Debugln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l singLogger) Debug(args ...any) {
|
||||
Debugln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l singLogger) Info(args ...any) {
|
||||
Infoln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l singLogger) Warn(args ...any) {
|
||||
Warnln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l singLogger) Error(args ...any) {
|
||||
Errorln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l singLogger) Fatal(args ...any) {
|
||||
Fatalln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l singLogger) Panic(args ...any) {
|
||||
Fatalln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
var SingLogger L.ContextLogger = singLogger{}
|
@ -20,6 +20,7 @@ import (
|
||||
"github.com/Dreamacro/clash/common/buf"
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
|
||||
"go.uber.org/atomic"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
@ -189,7 +190,7 @@ func (g *Conn) SetDeadline(t time.Time) error {
|
||||
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{}
|
||||
|
||||
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()
|
||||
|
||||
if len(Fingerprint) != 0 {
|
||||
if fingerprint, exists := tlsC.GetFingerprint(Fingerprint); exists {
|
||||
utlsConn := tlsC.UClient(pconn, cfg, fingerprint)
|
||||
if err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx); err != nil {
|
||||
if realityConfig == nil {
|
||||
if fingerprint, exists := tlsC.GetFingerprint(Fingerprint); exists {
|
||||
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()
|
||||
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
|
||||
//state := realityConn.(*utls.UConn).ConnectionState()
|
||||
//if p := state.NegotiatedProtocol; p != http2.NextProtoTLS {
|
||||
// realityConn.Close()
|
||||
// return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS)
|
||||
//}
|
||||
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)
|
||||
if err := conn.HandshakeContext(ctx); err != nil {
|
||||
@ -274,11 +292,11 @@ func StreamGunWithTransport(transport *TransportWrap, cfg *Config) (net.Conn, er
|
||||
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) {
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
transport := NewHTTP2Client(dialFn, tlsConfig, cfg.ClientFingerprint)
|
||||
transport := NewHTTP2Client(dialFn, tlsConfig, cfg.ClientFingerprint, realityConfig)
|
||||
return StreamGunWithTransport(transport, cfg)
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package udp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -12,6 +11,8 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/transport/hysteria/obfs"
|
||||
"github.com/Dreamacro/clash/transport/hysteria/utils"
|
||||
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -85,7 +86,7 @@ func NewObfsUDPHopClientPacketConn(server string, serverPorts string, hopInterva
|
||||
serverAddrs: serverAddrs,
|
||||
hopInterval: hopInterval,
|
||||
obfs: obfs,
|
||||
addrIndex: rand.Intn(len(serverAddrs)),
|
||||
addrIndex: fastrand.Intn(len(serverAddrs)),
|
||||
recvQueue: make(chan *udpPacket, packetQueueSize),
|
||||
closeChan: make(chan struct{}),
|
||||
bufPool: sync.Pool{
|
||||
@ -176,7 +177,7 @@ func (c *ObfsUDPHopClientPacketConn) hop(dialer utils.PacketDialer, rAddr net.Ad
|
||||
_ = trySetPacketConnWriteBuffer(c.currentConn, c.writeBufferSize)
|
||||
}
|
||||
go c.recvRoutine(c.currentConn)
|
||||
c.addrIndex = rand.Intn(len(c.serverAddrs))
|
||||
c.addrIndex = fastrand.Intn(len(c.serverAddrs))
|
||||
}
|
||||
|
||||
func (c *ObfsUDPHopClientPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
|
@ -2,12 +2,14 @@ package wechat
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/transport/hysteria/obfs"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/transport/hysteria/obfs"
|
||||
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
const udpBufferSize = 65535
|
||||
@ -29,7 +31,7 @@ func NewObfsWeChatUDPConn(orig net.PacketConn, obfs obfs.Obfuscator) *ObfsWeChat
|
||||
obfs: obfs,
|
||||
readBuf: make([]byte, udpBufferSize),
|
||||
writeBuf: make([]byte, udpBufferSize),
|
||||
sn: rand.Uint32() & 0xFFFF,
|
||||
sn: fastrand.Uint32() & 0xFFFF,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,20 +6,20 @@ import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/lunixbochs/struc"
|
||||
"github.com/metacubex/quic-go"
|
||||
"github.com/metacubex/quic-go/congestion"
|
||||
|
||||
"github.com/Dreamacro/clash/transport/hysteria/obfs"
|
||||
"github.com/Dreamacro/clash/transport/hysteria/pmtud_fix"
|
||||
"github.com/Dreamacro/clash/transport/hysteria/transport"
|
||||
"github.com/Dreamacro/clash/transport/hysteria/utils"
|
||||
|
||||
"github.com/lunixbochs/struc"
|
||||
"github.com/metacubex/quic-go"
|
||||
"github.com/metacubex/quic-go/congestion"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -408,7 +408,7 @@ func (c *quicPktConn) WriteTo(p []byte, addr string) error {
|
||||
if err != nil {
|
||||
if errSize, ok := err.(quic.ErrMessageTooLarge); ok {
|
||||
// need to frag
|
||||
msg.MsgID = uint16(rand.Intn(0xFFFF)) + 1 // msgID must be > 0 when fragCount > 1
|
||||
msg.MsgID = uint16(fastrand.Intn(0xFFFF)) + 1 // msgID must be > 0 when fragCount > 1
|
||||
fragMsgs := fragUDPMessage(msg, int(errSize))
|
||||
for _, fragMsg := range fragMsgs {
|
||||
msgBuf.Reset()
|
||||
|
@ -2,9 +2,8 @@ package obfs
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
// [salt][obfuscated payload]
|
||||
@ -12,16 +11,12 @@ import (
|
||||
const saltLen = 16
|
||||
|
||||
type XPlusObfuscator struct {
|
||||
Key []byte
|
||||
RandSrc *rand.Rand
|
||||
|
||||
lk sync.Mutex
|
||||
Key []byte
|
||||
}
|
||||
|
||||
func NewXPlusObfuscator(key []byte) *XPlusObfuscator {
|
||||
return &XPlusObfuscator{
|
||||
Key: key,
|
||||
RandSrc: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
Key: key,
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,9 +35,7 @@ func (x *XPlusObfuscator) Deobfuscate(in []byte, out []byte) int {
|
||||
}
|
||||
|
||||
func (x *XPlusObfuscator) Obfuscate(in []byte, out []byte) int {
|
||||
x.lk.Lock()
|
||||
_, _ = x.RandSrc.Read(out[:saltLen]) // salt
|
||||
x.lk.Unlock()
|
||||
_, _ = fastrand.Read(out[:saltLen]) // salt
|
||||
// Obfuscate the payload
|
||||
key := sha256.Sum256(append(x.Key, out[:saltLen]...))
|
||||
for i, c := range in {
|
||||
|
57
transport/restls/restls.go
Normal file
57
transport/restls/restls.go
Normal file
@ -0,0 +1,57 @@
|
||||
package restls
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
tls "github.com/3andne/restls-client-go"
|
||||
)
|
||||
|
||||
const (
|
||||
Mode string = "restls"
|
||||
)
|
||||
|
||||
// Restls
|
||||
type Restls struct {
|
||||
net.Conn
|
||||
firstPacketCache []byte
|
||||
firstPacket bool
|
||||
}
|
||||
|
||||
func (r *Restls) Read(b []byte) (int, error) {
|
||||
if err := r.Conn.(*tls.UConn).Handshake(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, err := r.Conn.(*tls.UConn).Read(b)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (r *Restls) Write(b []byte) (int, error) {
|
||||
if r.firstPacket {
|
||||
r.firstPacketCache = append([]byte(nil), b...)
|
||||
r.firstPacket = false
|
||||
return len(b), nil
|
||||
}
|
||||
if len(r.firstPacketCache) != 0 {
|
||||
b = append(r.firstPacketCache, b...)
|
||||
r.firstPacketCache = nil
|
||||
}
|
||||
n, err := r.Conn.(*tls.UConn).Write(b)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// NewRestls return a Restls Connection
|
||||
func NewRestls(conn net.Conn, config *tls.Config) (net.Conn, error) {
|
||||
if config != nil {
|
||||
clientIDPtr := config.ClientID.Load()
|
||||
if clientIDPtr != nil {
|
||||
return &Restls{
|
||||
Conn: tls.UClient(conn, config, *clientIDPtr),
|
||||
firstPacket: true,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return &Restls{
|
||||
Conn: tls.UClient(conn, config, tls.HelloChrome_Auto),
|
||||
firstPacket: true,
|
||||
}, nil
|
||||
}
|
@ -5,11 +5,12 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
// HTTPObfs is shadowsocks http simple-obfs implementation
|
||||
@ -63,9 +64,9 @@ func (ho *HTTPObfs) Read(b []byte) (int, error) {
|
||||
func (ho *HTTPObfs) Write(b []byte) (int, error) {
|
||||
if ho.firstRequest {
|
||||
randBytes := make([]byte, 16)
|
||||
rand.Read(randBytes)
|
||||
fastrand.Read(randBytes)
|
||||
req, _ := http.NewRequest("GET", fmt.Sprintf("http://%s/", ho.host), bytes.NewBuffer(b[:]))
|
||||
req.Header.Set("User-Agent", fmt.Sprintf("curl/7.%d.%d", rand.Int()%54, rand.Int()%2))
|
||||
req.Header.Set("User-Agent", fmt.Sprintf("curl/7.%d.%d", fastrand.Int()%54, fastrand.Int()%2))
|
||||
req.Header.Set("Upgrade", "websocket")
|
||||
req.Header.Set("Connection", "Upgrade")
|
||||
req.Host = ho.host
|
||||
|
@ -4,16 +4,13 @@ import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().Unix())
|
||||
}
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
const (
|
||||
chunkSize = 1 << 14 // 2 ** 14 == 16 * 1024
|
||||
@ -130,8 +127,8 @@ func NewTLSObfs(conn net.Conn, server string) net.Conn {
|
||||
func makeClientHelloMsg(data []byte, server string) []byte {
|
||||
random := make([]byte, 28)
|
||||
sessionID := make([]byte, 32)
|
||||
rand.Read(random)
|
||||
rand.Read(sessionID)
|
||||
fastrand.Read(random)
|
||||
fastrand.Read(sessionID)
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
|
91
transport/sing-shadowtls/shadowtls.go
Normal file
91
transport/sing-shadowtls/shadowtls.go
Normal file
@ -0,0 +1,91 @@
|
||||
package sing_shadowtls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/sagernet/sing-shadowtls"
|
||||
sing_common "github.com/sagernet/sing/common"
|
||||
utls "github.com/sagernet/utls"
|
||||
)
|
||||
|
||||
const (
|
||||
Mode string = "shadow-tls"
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultALPN = []string{"h2", "http/1.1"}
|
||||
)
|
||||
|
||||
type ShadowTLSOption struct {
|
||||
Password string
|
||||
Host string
|
||||
Fingerprint string
|
||||
ClientFingerprint string
|
||||
SkipCertVerify bool
|
||||
Version int
|
||||
}
|
||||
|
||||
func NewShadowTLS(ctx context.Context, conn net.Conn, option *ShadowTLSOption) (net.Conn, error) {
|
||||
tlsConfig := &tls.Config{
|
||||
NextProtos: DefaultALPN,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
ServerName: option.Host,
|
||||
}
|
||||
|
||||
var err error
|
||||
if len(option.Fingerprint) == 0 {
|
||||
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
|
||||
} else {
|
||||
if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
tlsHandshake := shadowtls.DefaultTLSHandshakeFunc(option.Password, tlsConfig)
|
||||
if len(option.ClientFingerprint) != 0 {
|
||||
if fingerprint, exists := tlsC.GetFingerprint(option.ClientFingerprint); exists {
|
||||
tlsHandshake = uTLSHandshakeFunc(tlsConfig, *fingerprint.ClientHelloID)
|
||||
}
|
||||
}
|
||||
client, err := shadowtls.NewClient(shadowtls.ClientConfig{
|
||||
Version: option.Version,
|
||||
Password: option.Password,
|
||||
TLSHandshake: tlsHandshake,
|
||||
Logger: log.SingLogger,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client.DialContextConn(ctx, conn)
|
||||
}
|
||||
|
||||
func uTLSHandshakeFunc(config *tls.Config, clientHelloID utls.ClientHelloID) shadowtls.TLSHandshakeFunc {
|
||||
return func(ctx context.Context, conn net.Conn, sessionIDGenerator shadowtls.TLSSessionIDGeneratorFunc) error {
|
||||
tlsConfig := &utls.Config{
|
||||
Rand: config.Rand,
|
||||
Time: config.Time,
|
||||
VerifyPeerCertificate: config.VerifyPeerCertificate,
|
||||
RootCAs: config.RootCAs,
|
||||
NextProtos: config.NextProtos,
|
||||
ServerName: config.ServerName,
|
||||
InsecureSkipVerify: config.InsecureSkipVerify,
|
||||
CipherSuites: config.CipherSuites,
|
||||
MinVersion: config.MinVersion,
|
||||
MaxVersion: config.MaxVersion,
|
||||
CurvePreferences: sing_common.Map(config.CurvePreferences, func(it tls.CurveID) utls.CurveID {
|
||||
return utls.CurveID(it)
|
||||
}),
|
||||
SessionTicketsDisabled: config.SessionTicketsDisabled,
|
||||
Renegotiation: utls.RenegotiationSupport(config.Renegotiation),
|
||||
SessionIDGenerator: sessionIDGenerator,
|
||||
}
|
||||
tlsConn := utls.UClient(conn, tlsConfig, clientHelloID)
|
||||
return tlsConn.HandshakeContext(ctx)
|
||||
}
|
||||
}
|
@ -4,12 +4,13 @@ import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -81,7 +82,7 @@ func (c *httpConn) Write(b []byte) (int, error) {
|
||||
bLength := len(b)
|
||||
headDataLength := bLength
|
||||
if bLength-headLength > 64 {
|
||||
headDataLength = headLength + rand.Intn(65)
|
||||
headDataLength = headLength + fastrand.Intn(65)
|
||||
}
|
||||
headData := b[:headDataLength]
|
||||
b = b[headDataLength:]
|
||||
@ -99,7 +100,7 @@ func (c *httpConn) Write(b []byte) (int, error) {
|
||||
}
|
||||
}
|
||||
hosts := strings.Split(host, ",")
|
||||
host = hosts[rand.Intn(len(hosts))]
|
||||
host = hosts[fastrand.Intn(len(hosts))]
|
||||
|
||||
buf := pool.GetBuffer()
|
||||
defer pool.PutBuffer(buf)
|
||||
@ -118,7 +119,7 @@ func (c *httpConn) Write(b []byte) (int, error) {
|
||||
buf.WriteString(body + "\r\n\r\n")
|
||||
} else {
|
||||
buf.WriteString("User-Agent: ")
|
||||
buf.WriteString(userAgent[rand.Intn(len(userAgent))])
|
||||
buf.WriteString(userAgent[fastrand.Intn(len(userAgent))])
|
||||
buf.WriteString("\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-US,en;q=0.8\r\nAccept-Encoding: gzip, deflate\r\n")
|
||||
if c.post {
|
||||
packBoundary(buf)
|
||||
@ -146,7 +147,7 @@ func packBoundary(buf *bytes.Buffer) {
|
||||
buf.WriteString("Content-Type: multipart/form-data; boundary=")
|
||||
set := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
for i := 0; i < 32; i++ {
|
||||
buf.WriteByte(set[rand.Intn(62)])
|
||||
buf.WriteByte(set[fastrand.Intn(62)])
|
||||
}
|
||||
buf.WriteString("\r\n")
|
||||
}
|
||||
|
@ -3,10 +3,11 @@ package obfs
|
||||
import (
|
||||
"encoding/binary"
|
||||
"hash/crc32"
|
||||
"math/rand"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -53,10 +54,10 @@ func (c *randomHeadConn) Write(b []byte) (int, error) {
|
||||
c.buf = append(c.buf, b...)
|
||||
if !c.hasSentHeader {
|
||||
c.hasSentHeader = true
|
||||
dataLength := rand.Intn(96) + 4
|
||||
dataLength := fastrand.Intn(96) + 4
|
||||
buf := pool.Get(dataLength + 4)
|
||||
defer pool.Put(buf)
|
||||
rand.Read(buf[:dataLength])
|
||||
fastrand.Read(buf[:dataLength])
|
||||
binary.LittleEndian.PutUint32(buf[dataLength:], 0xffffffff-crc32.ChecksumIEEE(buf[:dataLength]))
|
||||
_, err := c.Conn.Write(buf)
|
||||
return len(b), err
|
||||
|
@ -4,13 +4,14 @@ import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"encoding/binary"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/transport/ssr/tools"
|
||||
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -25,7 +26,7 @@ type tls12Ticket struct {
|
||||
|
||||
func newTLS12Ticket(b *Base) Obfs {
|
||||
r := &tls12Ticket{Base: b, authData: &authData{}}
|
||||
rand.Read(r.clientID[:])
|
||||
fastrand.Read(r.clientID[:])
|
||||
return r
|
||||
}
|
||||
|
||||
@ -90,7 +91,7 @@ func (c *tls12TicketConn) Write(b []byte) (int, error) {
|
||||
buf := pool.GetBuffer()
|
||||
defer pool.PutBuffer(buf)
|
||||
for len(b) > 2048 {
|
||||
size := rand.Intn(4096) + 100
|
||||
size := fastrand.Intn(4096) + 100
|
||||
if len(b) < size {
|
||||
size = len(b)
|
||||
}
|
||||
@ -196,7 +197,7 @@ func packSNIData(buf *bytes.Buffer, u string) {
|
||||
}
|
||||
|
||||
func (c *tls12TicketConn) packTicketBuf(buf *bytes.Buffer, u string) {
|
||||
length := 16 * (rand.Intn(17) + 8)
|
||||
length := 16 * (fastrand.Intn(17) + 8)
|
||||
buf.Write([]byte{0, 0x23})
|
||||
binary.Write(buf, binary.BigEndian, uint16(length))
|
||||
tools.AppendRandBytes(buf, length)
|
||||
@ -221,6 +222,6 @@ func (t *tls12Ticket) getHost() string {
|
||||
host = ""
|
||||
}
|
||||
hosts := strings.Split(host, ",")
|
||||
host = hosts[rand.Intn(len(hosts))]
|
||||
host = hosts[fastrand.Intn(len(hosts))]
|
||||
return host
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -12,6 +11,8 @@ import (
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/transport/ssr/tools"
|
||||
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
type (
|
||||
@ -64,7 +65,7 @@ func (a *authAES128) initUserData() {
|
||||
}
|
||||
if len(a.userKey) == 0 {
|
||||
a.userKey = a.Key
|
||||
rand.Read(a.userID[:])
|
||||
fastrand.Read(a.userID[:])
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,7 +199,7 @@ func (a *authAES128) packData(poolBuf *bytes.Buffer, data []byte, fullDataLength
|
||||
}
|
||||
|
||||
func trapezoidRandom(max int, d float64) int {
|
||||
base := rand.Float64()
|
||||
base := fastrand.Float64()
|
||||
if d-0 > 1e-6 {
|
||||
a := 1 - d
|
||||
base = (math.Sqrt(a*a+4*d*base) - a) / (2 * d)
|
||||
@ -219,10 +220,10 @@ func (a *authAES128) getRandDataLengthForPackData(dataLength, fullDataLength int
|
||||
if revLength > -1460 {
|
||||
return trapezoidRandom(revLength+1460, -0.3)
|
||||
}
|
||||
return rand.Intn(32)
|
||||
return fastrand.Intn(32)
|
||||
}
|
||||
if dataLength > 900 {
|
||||
return rand.Intn(revLength)
|
||||
return fastrand.Intn(revLength)
|
||||
}
|
||||
return trapezoidRandom(revLength, -0.3)
|
||||
}
|
||||
@ -247,7 +248,7 @@ func (a *authAES128) packAuthData(poolBuf *bytes.Buffer, data []byte) {
|
||||
copy(macKey, a.iv)
|
||||
copy(macKey[len(a.iv):], a.Key)
|
||||
|
||||
poolBuf.WriteByte(byte(rand.Intn(256)))
|
||||
poolBuf.WriteByte(byte(fastrand.Intn(256)))
|
||||
poolBuf.Write(a.hmac(macKey, poolBuf.Bytes())[:6])
|
||||
poolBuf.Write(a.userID[:])
|
||||
err := a.authData.putEncryptedData(poolBuf, a.userKey, [2]int{packedAuthDataLength, randDataLength}, a.salt)
|
||||
@ -263,9 +264,9 @@ func (a *authAES128) packAuthData(poolBuf *bytes.Buffer, data []byte) {
|
||||
|
||||
func (a *authAES128) getRandDataLengthForPackAuthData(size int) int {
|
||||
if size > 400 {
|
||||
return rand.Intn(512)
|
||||
return fastrand.Intn(512)
|
||||
}
|
||||
return rand.Intn(1024)
|
||||
return fastrand.Intn(1024)
|
||||
}
|
||||
|
||||
func (a *authAES128) packRandData(poolBuf *bytes.Buffer, size int) {
|
||||
|
@ -5,11 +5,12 @@ import (
|
||||
"encoding/binary"
|
||||
"hash/adler32"
|
||||
"hash/crc32"
|
||||
"math/rand"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/transport/ssr/tools"
|
||||
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -176,7 +177,7 @@ func (a *authSHA1V4) getRandDataLength(size int) int {
|
||||
return 0
|
||||
}
|
||||
if size > 400 {
|
||||
return rand.Intn(256)
|
||||
return fastrand.Intn(256)
|
||||
}
|
||||
return rand.Intn(512)
|
||||
return fastrand.Intn(512)
|
||||
}
|
||||
|
@ -6,13 +6,14 @@ import (
|
||||
"crypto/cipher"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/transport/shadowsocks/core"
|
||||
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
type Base struct {
|
||||
@ -37,8 +38,8 @@ func (a *authData) next() *authData {
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
if a.connectionID > 0xff000000 || a.connectionID == 0 {
|
||||
rand.Read(a.clientID[:])
|
||||
a.connectionID = rand.Uint32() & 0xffffff
|
||||
fastrand.Read(a.clientID[:])
|
||||
a.connectionID = fastrand.Uint32() & 0xffffff
|
||||
}
|
||||
a.connectionID++
|
||||
copy(r.clientID[:], a.clientID[:])
|
||||
|
@ -4,8 +4,9 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -68,7 +69,7 @@ func getHeadSize(b []byte, defaultValue int) int {
|
||||
|
||||
func getDataLength(b []byte) int {
|
||||
bLength := len(b)
|
||||
dataLength := getHeadSize(b, 30) + rand.Intn(32)
|
||||
dataLength := getHeadSize(b, 30) + fastrand.Intn(32)
|
||||
if bLength < dataLength {
|
||||
return bLength
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
"github.com/Dreamacro/clash/transport/vless"
|
||||
"github.com/Dreamacro/clash/transport/vmess"
|
||||
|
||||
xtls "github.com/xtls/go"
|
||||
)
|
||||
|
||||
@ -54,6 +55,7 @@ type Option struct {
|
||||
Flow string
|
||||
FlowShow bool
|
||||
ClientFingerprint string
|
||||
Reality *tlsC.RealityConfig
|
||||
}
|
||||
|
||||
type WebsocketOption struct {
|
||||
@ -117,16 +119,24 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) {
|
||||
}
|
||||
|
||||
if len(t.option.ClientFingerprint) != 0 {
|
||||
utlsConn, valid := vmess.GetUtlsConnWithClientFingerprint(conn, t.option.ClientFingerprint, tlsConfig)
|
||||
if valid {
|
||||
if t.option.Reality == nil {
|
||||
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)
|
||||
defer cancel()
|
||||
|
||||
err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx)
|
||||
return utlsConn, err
|
||||
|
||||
return tlsC.GetRealityConn(ctx, conn, t.option.ClientFingerprint, tlsConfig, t.option.Reality)
|
||||
}
|
||||
}
|
||||
if t.option.Reality != nil {
|
||||
return nil, errors.New("REALITY is based on uTLS, please set a client-fingerprint")
|
||||
}
|
||||
|
||||
tlsConn := tls.Client(conn, tlsConfig)
|
||||
|
||||
|
@ -6,19 +6,19 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"net"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/quic-go"
|
||||
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/metacubex/quic-go"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -352,7 +352,7 @@ func (t *clientImpl) ListenPacketWithDialer(ctx context.Context, metadata *C.Met
|
||||
pipe1, pipe2 := net.Pipe()
|
||||
var connId uint32
|
||||
for {
|
||||
connId = rand.Uint32()
|
||||
connId = fastrand.Uint32()
|
||||
_, loaded := t.udpInputMap.LoadOrStore(connId, pipe1)
|
||||
if !loaded {
|
||||
break
|
||||
|
@ -5,11 +5,11 @@ package congestion
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/quic-go/congestion"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -780,7 +780,7 @@ func (b *bbrSender) EnterProbeBandwidthMode(now time.Time) {
|
||||
// Pick a random offset for the gain cycle out of {0, 2..7} range. 1 is
|
||||
// excluded because in that case increased gain and decreased gain would not
|
||||
// follow each other.
|
||||
b.cycleCurrentOffset = rand.Int() % (GainCycleLength - 1)
|
||||
b.cycleCurrentOffset = fastrand.Int() % (GainCycleLength - 1)
|
||||
if b.cycleCurrentOffset >= 1 {
|
||||
b.cycleCurrentOffset += 1
|
||||
}
|
||||
|
@ -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) {
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}()
|
||||
}
|
||||
addr.String()
|
||||
buf := pool.GetBuffer()
|
||||
defer pool.PutBuffer(buf)
|
||||
addrPort, err := netip.ParseAddrPort(addr.String())
|
||||
@ -239,7 +238,8 @@ func (q *quicStreamPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err erro
|
||||
return
|
||||
}
|
||||
default: // native
|
||||
err = q.quicConn.SendMessage(buf.Bytes())
|
||||
data := buf.Bytes()
|
||||
err = q.quicConn.SendMessage(data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -250,7 +250,29 @@ func (q *quicStreamPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err erro
|
||||
}
|
||||
|
||||
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{}
|
||||
|
||||
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) {
|
||||
c.CommandHead = head
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if c.CommandHead.TYPE != AuthenticateType {
|
||||
err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE)
|
||||
return
|
||||
@ -170,9 +167,6 @@ func NewConnect(ADDR Address) Connect {
|
||||
|
||||
func ReadConnectWithHead(head CommandHead, reader BufferedReader) (c Connect, err error) {
|
||||
c.CommandHead = head
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if c.CommandHead.TYPE != ConnectType {
|
||||
err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE)
|
||||
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) {
|
||||
c.CommandHead = head
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if c.CommandHead.TYPE != PacketType {
|
||||
err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE)
|
||||
return
|
||||
@ -305,9 +296,6 @@ func NewDissociate(ASSOC_ID uint32) Dissociate {
|
||||
|
||||
func ReadDissociateWithHead(head CommandHead, reader BufferedReader) (c Dissociate, err error) {
|
||||
c.CommandHead = head
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if c.CommandHead.TYPE != DissociateType {
|
||||
err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE)
|
||||
return
|
||||
@ -476,15 +464,17 @@ func NewAddress(metadata *C.Metadata) Address {
|
||||
|
||||
func NewAddressAddrPort(addrPort netip.AddrPort) Address {
|
||||
var addrType byte
|
||||
if addrPort.Addr().Is4() {
|
||||
port := addrPort.Port()
|
||||
addr := addrPort.Addr().Unmap()
|
||||
if addr.Is4() {
|
||||
addrType = AtypIPv4
|
||||
} else {
|
||||
addrType = AtypIPv6
|
||||
}
|
||||
return Address{
|
||||
TYPE: addrType,
|
||||
ADDR: addrPort.Addr().AsSlice(),
|
||||
PORT: addrPort.Port(),
|
||||
ADDR: addr.AsSlice(),
|
||||
PORT: port,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,19 +5,19 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/metacubex/quic-go"
|
||||
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/metacubex/quic-go"
|
||||
)
|
||||
|
||||
type ServerOption struct {
|
||||
@ -55,7 +55,7 @@ func (s *Server) Serve() error {
|
||||
return err
|
||||
}
|
||||
SetCongestionController(conn, s.CongestionController)
|
||||
uuid, err := uuid.NewV4()
|
||||
uuid, err := utils.UnsafeUUIDGenerator.NewV4()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -75,7 +75,7 @@ func (s *Server) Close() error {
|
||||
|
||||
type serverHandler struct {
|
||||
*Server
|
||||
quicConn quic.Connection
|
||||
quicConn quic.EarlyConnection
|
||||
uuid uuid.UUID
|
||||
|
||||
authCh chan struct{}
|
||||
@ -86,13 +86,6 @@ type serverHandler struct {
|
||||
}
|
||||
|
||||
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() {
|
||||
_ = s.handleUniStream()
|
||||
}()
|
||||
@ -102,6 +95,15 @@ func (s *serverHandler) handle() {
|
||||
go func() {
|
||||
_ = 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) {
|
||||
@ -151,14 +153,10 @@ func (s *serverHandler) parsePacket(packet Packet, udpRelayMode string) (err err
|
||||
return s.HandleUdpFn(packet.ADDR.SocksAddr(), &serverUDPPacket{
|
||||
pc: pc,
|
||||
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) {
|
||||
for {
|
||||
var quicStream quic.Stream
|
||||
@ -238,7 +236,7 @@ func (s *serverHandler) handleUniStream() (err error) {
|
||||
}
|
||||
s.authOnce.Do(func() {
|
||||
if !ok {
|
||||
_ = s.quicConn.CloseWithError(AuthenticationFailed, "")
|
||||
_ = s.quicConn.CloseWithError(AuthenticationFailed, "AuthenticationFailed")
|
||||
}
|
||||
s.authOk = ok
|
||||
close(s.authCh)
|
||||
@ -273,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 {
|
||||
pc *quicStreamPacketConn
|
||||
packet *Packet
|
||||
|
@ -1,105 +1,298 @@
|
||||
package vless
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/subtle"
|
||||
gotls "crypto/tls"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Dreamacro/clash/common/buf"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
utls "github.com/sagernet/utls"
|
||||
xtls "github.com/xtls/go"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type Conn struct {
|
||||
N.ExtendedConn
|
||||
N.ExtendedWriter
|
||||
N.ExtendedReader
|
||||
net.Conn
|
||||
dst *DstAddr
|
||||
id *uuid.UUID
|
||||
addons *Addons
|
||||
received bool
|
||||
|
||||
handshake chan struct{}
|
||||
handshakeMutex sync.Mutex
|
||||
needHandshake bool
|
||||
err error
|
||||
|
||||
tlsConn net.Conn
|
||||
input *bytes.Reader
|
||||
rawInput *bytes.Buffer
|
||||
|
||||
packetsToFilter int
|
||||
isTLS bool
|
||||
isTLS12orAbove bool
|
||||
enableXTLS bool
|
||||
cipher uint16
|
||||
remainingServerHello uint16
|
||||
readRemainingContent int
|
||||
readRemainingPadding int
|
||||
readProcess bool
|
||||
readFilterUUID bool
|
||||
readLastCommand byte
|
||||
writeFilterApplicationData bool
|
||||
writeDirect bool
|
||||
}
|
||||
|
||||
func (vc *Conn) Read(b []byte) (int, error) {
|
||||
if vc.received {
|
||||
return vc.ExtendedConn.Read(b)
|
||||
if vc.readProcess {
|
||||
buffer := buf.With(b)
|
||||
err := vc.ReadBuffer(buffer)
|
||||
return buffer.Len(), err
|
||||
}
|
||||
return vc.ExtendedReader.Read(b)
|
||||
}
|
||||
|
||||
if err := vc.recvResponse(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
vc.received = true
|
||||
return vc.ExtendedConn.Read(b)
|
||||
return vc.Read(b)
|
||||
}
|
||||
|
||||
func (vc *Conn) ReadBuffer(buffer *buf.Buffer) error {
|
||||
if vc.received {
|
||||
return vc.ExtendedConn.ReadBuffer(buffer)
|
||||
toRead := buffer.FreeBytes()
|
||||
if vc.readRemainingContent > 0 {
|
||||
if vc.readRemainingContent < buffer.FreeLen() {
|
||||
toRead = toRead[:vc.readRemainingContent]
|
||||
}
|
||||
n, err := vc.ExtendedReader.Read(toRead)
|
||||
buffer.Truncate(n)
|
||||
vc.readRemainingContent -= n
|
||||
vc.FilterTLS(toRead)
|
||||
return err
|
||||
}
|
||||
if vc.readRemainingPadding > 0 {
|
||||
_, err := io.CopyN(io.Discard, vc.ExtendedReader, int64(vc.readRemainingPadding))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vc.readRemainingPadding = 0
|
||||
}
|
||||
if vc.readProcess {
|
||||
switch vc.readLastCommand {
|
||||
case commandPaddingContinue:
|
||||
//if vc.isTLS || vc.packetsToFilter > 0 {
|
||||
headerUUIDLen := 0
|
||||
if vc.readFilterUUID {
|
||||
headerUUIDLen = uuid.Size
|
||||
}
|
||||
var header []byte
|
||||
if need := headerUUIDLen + paddingHeaderLen; buffer.FreeLen() < need {
|
||||
header = make([]byte, need)
|
||||
} else {
|
||||
header = buffer.FreeBytes()[:need]
|
||||
}
|
||||
_, err := io.ReadFull(vc.ExtendedReader, header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pos := 0
|
||||
if vc.readFilterUUID {
|
||||
vc.readFilterUUID = false
|
||||
pos = uuid.Size
|
||||
if subtle.ConstantTimeCompare(vc.id.Bytes(), header[:uuid.Size]) != 1 {
|
||||
err = fmt.Errorf("XTLS Vision server responded unknown UUID: %s",
|
||||
uuid.FromBytesOrNil(header[:uuid.Size]).String())
|
||||
log.Errorln(err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
vc.readLastCommand = header[pos]
|
||||
vc.readRemainingContent = int(binary.BigEndian.Uint16(header[pos+1:]))
|
||||
vc.readRemainingPadding = int(binary.BigEndian.Uint16(header[pos+3:]))
|
||||
log.Debugln("XTLS Vision read padding: command=%d, payloadLen=%d, paddingLen=%d",
|
||||
vc.readLastCommand, vc.readRemainingContent, vc.readRemainingPadding)
|
||||
return vc.ReadBuffer(buffer)
|
||||
//}
|
||||
case commandPaddingEnd:
|
||||
vc.readProcess = false
|
||||
return vc.ReadBuffer(buffer)
|
||||
case commandPaddingDirect:
|
||||
needReturn := false
|
||||
if vc.input != nil {
|
||||
_, err := buffer.ReadFrom(vc.input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if vc.input.Len() == 0 {
|
||||
needReturn = true
|
||||
vc.input = nil
|
||||
} else { // buffer is full
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if vc.rawInput != nil {
|
||||
_, err := buffer.ReadFrom(vc.rawInput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
needReturn = true
|
||||
if vc.rawInput.Len() == 0 {
|
||||
vc.rawInput = nil
|
||||
}
|
||||
}
|
||||
if vc.input == nil && vc.rawInput == nil {
|
||||
vc.readProcess = false
|
||||
vc.ExtendedReader = N.NewExtendedReader(vc.Conn)
|
||||
log.Debugln("XTLS Vision direct read start")
|
||||
}
|
||||
if needReturn {
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
err := fmt.Errorf("XTLS Vision read unknown command: %d", vc.readLastCommand)
|
||||
log.Debugln(err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
return vc.ExtendedReader.ReadBuffer(buffer)
|
||||
}
|
||||
|
||||
if err := vc.recvResponse(); err != nil {
|
||||
return err
|
||||
}
|
||||
vc.received = true
|
||||
return vc.ExtendedConn.ReadBuffer(buffer)
|
||||
return vc.ReadBuffer(buffer)
|
||||
}
|
||||
|
||||
func (vc *Conn) Write(p []byte) (int, error) {
|
||||
select {
|
||||
case <-vc.handshake:
|
||||
default:
|
||||
if vc.sendRequest(p) {
|
||||
if vc.needHandshake {
|
||||
vc.handshakeMutex.Lock()
|
||||
if vc.needHandshake {
|
||||
vc.needHandshake = false
|
||||
if vc.sendRequest(p) {
|
||||
vc.handshakeMutex.Unlock()
|
||||
if vc.err != nil {
|
||||
return 0, vc.err
|
||||
}
|
||||
return len(p), vc.err
|
||||
}
|
||||
if vc.err != nil {
|
||||
vc.handshakeMutex.Unlock()
|
||||
return 0, vc.err
|
||||
}
|
||||
return len(p), vc.err
|
||||
}
|
||||
if vc.err != nil {
|
||||
return 0, vc.err
|
||||
}
|
||||
vc.handshakeMutex.Unlock()
|
||||
}
|
||||
return vc.ExtendedConn.Write(p)
|
||||
|
||||
if vc.writeFilterApplicationData {
|
||||
_buffer := buf.StackNew()
|
||||
defer buf.KeepAlive(_buffer)
|
||||
buffer := buf.Dup(_buffer)
|
||||
defer buffer.Release()
|
||||
buffer.Write(p)
|
||||
err := vc.WriteBuffer(buffer)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
return vc.ExtendedWriter.Write(p)
|
||||
}
|
||||
|
||||
func (vc *Conn) WriteBuffer(buffer *buf.Buffer) error {
|
||||
select {
|
||||
case <-vc.handshake:
|
||||
default:
|
||||
if vc.sendRequest(buffer.Bytes()) {
|
||||
return vc.err
|
||||
}
|
||||
if vc.err != nil {
|
||||
return vc.err
|
||||
if vc.needHandshake {
|
||||
vc.handshakeMutex.Lock()
|
||||
if vc.needHandshake {
|
||||
vc.needHandshake = false
|
||||
if vc.sendRequest(buffer.Bytes()) {
|
||||
vc.handshakeMutex.Unlock()
|
||||
return vc.err
|
||||
}
|
||||
if vc.err != nil {
|
||||
vc.handshakeMutex.Unlock()
|
||||
return vc.err
|
||||
}
|
||||
}
|
||||
vc.handshakeMutex.Unlock()
|
||||
}
|
||||
return vc.ExtendedConn.WriteBuffer(buffer)
|
||||
|
||||
if vc.writeFilterApplicationData {
|
||||
buffer2 := ReshapeBuffer(buffer)
|
||||
defer buffer2.Release()
|
||||
vc.FilterTLS(buffer.Bytes())
|
||||
command := commandPaddingContinue
|
||||
if !vc.isTLS {
|
||||
command = commandPaddingEnd
|
||||
|
||||
// disable XTLS
|
||||
//vc.readProcess = false
|
||||
vc.writeFilterApplicationData = false
|
||||
vc.packetsToFilter = 0
|
||||
} else if buffer.Len() > 6 && bytes.Equal(buffer.To(3), tlsApplicationDataStart) || vc.packetsToFilter <= 0 {
|
||||
command = commandPaddingEnd
|
||||
if vc.enableXTLS {
|
||||
command = commandPaddingDirect
|
||||
vc.writeDirect = true
|
||||
}
|
||||
vc.writeFilterApplicationData = false
|
||||
}
|
||||
ApplyPadding(buffer, command, nil, vc.isTLS)
|
||||
err := vc.ExtendedWriter.WriteBuffer(buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if vc.writeDirect {
|
||||
vc.ExtendedWriter = N.NewExtendedWriter(vc.Conn)
|
||||
log.Debugln("XTLS Vision direct write start")
|
||||
//time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
if buffer2 != nil {
|
||||
if vc.writeDirect || !vc.isTLS {
|
||||
return vc.ExtendedWriter.WriteBuffer(buffer2)
|
||||
}
|
||||
vc.FilterTLS(buffer2.Bytes())
|
||||
command = commandPaddingContinue
|
||||
if buffer2.Len() > 6 && bytes.Equal(buffer2.To(3), tlsApplicationDataStart) || vc.packetsToFilter <= 0 {
|
||||
command = commandPaddingEnd
|
||||
if vc.enableXTLS {
|
||||
command = commandPaddingDirect
|
||||
vc.writeDirect = true
|
||||
}
|
||||
vc.writeFilterApplicationData = false
|
||||
}
|
||||
ApplyPadding(buffer2, command, nil, vc.isTLS)
|
||||
err = vc.ExtendedWriter.WriteBuffer(buffer2)
|
||||
if vc.writeDirect {
|
||||
vc.ExtendedWriter = N.NewExtendedWriter(vc.Conn)
|
||||
log.Debugln("XTLS Vision direct write start")
|
||||
//time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
/*if vc.writeDirect {
|
||||
log.Debugln("XTLS Vision Direct write, payloadLen=%d", buffer.Len())
|
||||
}*/
|
||||
return vc.ExtendedWriter.WriteBuffer(buffer)
|
||||
}
|
||||
|
||||
func (vc *Conn) sendRequest(p []byte) bool {
|
||||
vc.handshakeMutex.Lock()
|
||||
defer vc.handshakeMutex.Unlock()
|
||||
|
||||
select {
|
||||
case <-vc.handshake:
|
||||
// The handshake has been completed before.
|
||||
// So return false to remind the caller.
|
||||
return false
|
||||
default:
|
||||
}
|
||||
defer close(vc.handshake)
|
||||
|
||||
requestLen := 1 // protocol version
|
||||
requestLen += 16 // UUID
|
||||
requestLen += 1 // addons length
|
||||
var addonsBytes []byte
|
||||
if vc.addons != nil {
|
||||
addonsBytes, vc.err = proto.Marshal(vc.addons)
|
||||
@ -107,19 +300,32 @@ func (vc *Conn) sendRequest(p []byte) bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
requestLen += len(addonsBytes)
|
||||
requestLen += 1 // command
|
||||
if !vc.dst.Mux {
|
||||
requestLen += 2 // port
|
||||
requestLen += 1 // addr type
|
||||
requestLen += len(vc.dst.Addr)
|
||||
}
|
||||
requestLen += len(p)
|
||||
isVision := vc.IsXTLSVisionEnabled()
|
||||
|
||||
_buffer := buf.StackNewSize(requestLen)
|
||||
defer buf.KeepAlive(_buffer)
|
||||
buffer := buf.Dup(_buffer)
|
||||
defer buffer.Release()
|
||||
var buffer *buf.Buffer
|
||||
if isVision {
|
||||
_buffer := buf.StackNew()
|
||||
defer buf.KeepAlive(_buffer)
|
||||
buffer = buf.Dup(_buffer)
|
||||
defer buffer.Release()
|
||||
} else {
|
||||
requestLen := 1 // protocol version
|
||||
requestLen += 16 // UUID
|
||||
requestLen += 1 // addons length
|
||||
requestLen += len(addonsBytes)
|
||||
requestLen += 1 // command
|
||||
if !vc.dst.Mux {
|
||||
requestLen += 2 // port
|
||||
requestLen += 1 // addr type
|
||||
requestLen += len(vc.dst.Addr)
|
||||
}
|
||||
requestLen += len(p)
|
||||
|
||||
_buffer := buf.StackNewSize(requestLen)
|
||||
defer buf.KeepAlive(_buffer)
|
||||
buffer = buf.Dup(_buffer)
|
||||
defer buffer.Release()
|
||||
}
|
||||
|
||||
buf.Must(
|
||||
buffer.WriteByte(Version), // protocol version
|
||||
@ -144,47 +350,103 @@ func (vc *Conn) sendRequest(p []byte) bool {
|
||||
)
|
||||
}
|
||||
|
||||
buf.Must(buf.Error(buffer.Write(p)))
|
||||
if isVision && !vc.dst.UDP && !vc.dst.Mux {
|
||||
if len(p) == 0 {
|
||||
WriteWithPadding(buffer, nil, commandPaddingContinue, vc.id, vc.isTLS)
|
||||
} else {
|
||||
vc.FilterTLS(p)
|
||||
//if vc.isTLS {
|
||||
WriteWithPadding(buffer, p, commandPaddingContinue, vc.id, vc.isTLS)
|
||||
//} else {
|
||||
// buf.Must(buf.Error(buffer.Write(p)))
|
||||
//
|
||||
// // disable XTLS
|
||||
// vc.readProcess = false
|
||||
// vc.writeFilterApplicationData = false
|
||||
// vc.packetsToFilter = 0
|
||||
//}
|
||||
}
|
||||
} else {
|
||||
buf.Must(buf.Error(buffer.Write(p)))
|
||||
}
|
||||
|
||||
_, vc.err = vc.ExtendedConn.Write(buffer.Bytes())
|
||||
_, vc.err = vc.ExtendedWriter.Write(buffer.Bytes())
|
||||
if vc.err != nil {
|
||||
return true
|
||||
}
|
||||
if isVision {
|
||||
switch underlying := vc.tlsConn.(type) {
|
||||
case *gotls.Conn:
|
||||
if underlying.ConnectionState().Version != gotls.VersionTLS13 {
|
||||
vc.err = ErrNotTLS13
|
||||
}
|
||||
case *utls.UConn:
|
||||
if underlying.ConnectionState().Version != utls.VersionTLS13 {
|
||||
vc.err = ErrNotTLS13
|
||||
}
|
||||
default:
|
||||
vc.err = fmt.Errorf(`failed to use %s, maybe "security" is not "tls" or "utls"`, vc.addons.Flow)
|
||||
}
|
||||
vc.tlsConn = nil
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (vc *Conn) recvResponse() error {
|
||||
var buf [1]byte
|
||||
_, vc.err = io.ReadFull(vc.ExtendedConn, buf[:])
|
||||
var buffer [1]byte
|
||||
_, vc.err = io.ReadFull(vc.ExtendedReader, buffer[:])
|
||||
if vc.err != nil {
|
||||
return vc.err
|
||||
}
|
||||
|
||||
if buf[0] != Version {
|
||||
if buffer[0] != Version {
|
||||
return errors.New("unexpected response version")
|
||||
}
|
||||
|
||||
_, vc.err = io.ReadFull(vc.ExtendedConn, buf[:])
|
||||
_, vc.err = io.ReadFull(vc.ExtendedReader, buffer[:])
|
||||
if vc.err != nil {
|
||||
return vc.err
|
||||
}
|
||||
|
||||
length := int64(buf[0])
|
||||
length := int64(buffer[0])
|
||||
if length != 0 { // addon data length > 0
|
||||
io.CopyN(io.Discard, vc.ExtendedConn, length) // just discard
|
||||
io.CopyN(io.Discard, vc.ExtendedReader, length) // just discard
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vc *Conn) FrontHeadroom() int {
|
||||
if vc.IsXTLSVisionEnabled() {
|
||||
return paddingHeaderLen
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (vc *Conn) Upstream() any {
|
||||
return vc.ExtendedConn
|
||||
if vc.tlsConn == nil {
|
||||
return vc.Conn
|
||||
}
|
||||
return vc.tlsConn
|
||||
}
|
||||
|
||||
func (vc *Conn) NeedHandshake() bool {
|
||||
return vc.needHandshake
|
||||
}
|
||||
|
||||
func (vc *Conn) IsXTLSVisionEnabled() bool {
|
||||
return vc.addons != nil && vc.addons.Flow == XRV
|
||||
}
|
||||
|
||||
// newConn return a Conn instance
|
||||
func newConn(conn net.Conn, client *Client, dst *DstAddr) (*Conn, error) {
|
||||
c := &Conn{
|
||||
ExtendedConn: N.NewExtendedConn(conn),
|
||||
id: client.uuid,
|
||||
dst: dst,
|
||||
handshake: make(chan struct{}),
|
||||
ExtendedReader: N.NewExtendedReader(conn),
|
||||
ExtendedWriter: N.NewExtendedWriter(conn),
|
||||
Conn: conn,
|
||||
id: client.uuid,
|
||||
dst: dst,
|
||||
needHandshake: true,
|
||||
}
|
||||
|
||||
if !dst.UDP && client.Addons != nil {
|
||||
@ -205,15 +467,46 @@ func newConn(conn net.Conn, client *Client, dst *DstAddr) (*Conn, error) {
|
||||
} else {
|
||||
return nil, fmt.Errorf("failed to use %s, maybe \"security\" is not \"xtls\"", client.Addons.Flow)
|
||||
}
|
||||
case XRV:
|
||||
c.packetsToFilter = 6
|
||||
c.readProcess = true
|
||||
c.readFilterUUID = true
|
||||
c.writeFilterApplicationData = true
|
||||
c.addons = client.Addons
|
||||
var t reflect.Type
|
||||
var p uintptr
|
||||
switch underlying := conn.(type) {
|
||||
case *gotls.Conn:
|
||||
//log.Debugln("type tls")
|
||||
c.Conn = underlying.NetConn()
|
||||
c.tlsConn = underlying
|
||||
t = reflect.TypeOf(underlying).Elem()
|
||||
p = uintptr(unsafe.Pointer(underlying))
|
||||
case *utls.UConn:
|
||||
//log.Debugln("type *utls.UConn")
|
||||
c.Conn = underlying.NetConn()
|
||||
c.tlsConn = underlying
|
||||
t = reflect.TypeOf(underlying.Conn).Elem()
|
||||
p = uintptr(unsafe.Pointer(underlying.Conn))
|
||||
case *tlsC.UConn:
|
||||
//log.Debugln("type *tlsC.UConn")
|
||||
c.Conn = underlying.NetConn()
|
||||
c.tlsConn = underlying.UConn
|
||||
t = reflect.TypeOf(underlying.Conn).Elem()
|
||||
//log.Debugln("t:%v", t)
|
||||
p = uintptr(unsafe.Pointer(underlying.Conn))
|
||||
default:
|
||||
return nil, fmt.Errorf(`failed to use %s, maybe "security" is not "tls" or "utls"`, client.Addons.Flow)
|
||||
}
|
||||
i, _ := t.FieldByName("input")
|
||||
r, _ := t.FieldByName("rawInput")
|
||||
c.input = (*bytes.Reader)(unsafe.Pointer(p + i.Offset))
|
||||
c.rawInput = (*bytes.Buffer)(unsafe.Pointer(p + r.Offset))
|
||||
//if _, ok := c.Conn.(*net.TCPConn); !ok {
|
||||
// log.Debugln("XTLS underlying conn is not *net.TCPConn, got %T", c.Conn)
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-c.handshake:
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
c.sendRequest(nil)
|
||||
}
|
||||
}()
|
||||
return c, nil
|
||||
}
|
||||
|
90
transport/vless/filter.go
Normal file
90
transport/vless/filter.go
Normal file
@ -0,0 +1,90 @@
|
||||
package vless
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
var (
|
||||
tls13SupportedVersions = []byte{0x00, 0x2b, 0x00, 0x02, 0x03, 0x04}
|
||||
tlsClientHandshakeStart = []byte{0x16, 0x03}
|
||||
tlsServerHandshakeStart = []byte{0x16, 0x03, 0x03}
|
||||
tlsApplicationDataStart = []byte{0x17, 0x03, 0x03}
|
||||
|
||||
tls13CipherSuiteMap = map[uint16]string{
|
||||
0x1301: "TLS_AES_128_GCM_SHA256",
|
||||
0x1302: "TLS_AES_256_GCM_SHA384",
|
||||
0x1303: "TLS_CHACHA20_POLY1305_SHA256",
|
||||
0x1304: "TLS_AES_128_CCM_SHA256",
|
||||
0x1305: "TLS_AES_128_CCM_8_SHA256",
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
tlsHandshakeTypeClientHello byte = 0x01
|
||||
tlsHandshakeTypeServerHello byte = 0x02
|
||||
)
|
||||
|
||||
func (vc *Conn) FilterTLS(buffer []byte) (index int) {
|
||||
if vc.packetsToFilter <= 0 {
|
||||
return 0
|
||||
}
|
||||
lenP := len(buffer)
|
||||
vc.packetsToFilter--
|
||||
if index = bytes.Index(buffer, tlsServerHandshakeStart); index != -1 {
|
||||
if lenP >= index+5 {
|
||||
if buffer[0] == 22 && buffer[1] == 3 && buffer[2] == 3 {
|
||||
vc.isTLS = true
|
||||
if buffer[5] == tlsHandshakeTypeServerHello {
|
||||
//log.Debugln("isTLS12orAbove")
|
||||
vc.remainingServerHello = binary.BigEndian.Uint16(buffer[index+3:]) + 5
|
||||
vc.isTLS12orAbove = true
|
||||
if lenP-index >= 79 && vc.remainingServerHello >= 79 {
|
||||
sessionIDLen := int(buffer[index+43])
|
||||
vc.cipher = binary.BigEndian.Uint16(buffer[index+43+sessionIDLen+1:])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if index = bytes.Index(buffer, tlsClientHandshakeStart); index != -1 {
|
||||
if lenP >= index+5 && buffer[index+5] == tlsHandshakeTypeClientHello {
|
||||
vc.isTLS = true
|
||||
}
|
||||
}
|
||||
|
||||
if vc.remainingServerHello > 0 {
|
||||
end := int(vc.remainingServerHello)
|
||||
i := index
|
||||
if i < 0 {
|
||||
i = 0
|
||||
}
|
||||
if i+end > lenP {
|
||||
end = lenP
|
||||
vc.remainingServerHello -= uint16(end - i)
|
||||
} else {
|
||||
vc.remainingServerHello -= uint16(end)
|
||||
end += i
|
||||
}
|
||||
if bytes.Contains(buffer[i:end], tls13SupportedVersions) {
|
||||
// TLS 1.3 Client Hello
|
||||
cs, ok := tls13CipherSuiteMap[vc.cipher]
|
||||
if ok && cs != "TLS_AES_128_CCM_8_SHA256" {
|
||||
vc.enableXTLS = true
|
||||
}
|
||||
log.Debugln("XTLS Vision found TLS 1.3, packetLength=%d, CipherSuite=%s", lenP, cs)
|
||||
vc.packetsToFilter = 0
|
||||
return
|
||||
} else if vc.remainingServerHello <= 0 {
|
||||
log.Debugln("XTLS Vision found TLS 1.2, packetLength=%d", lenP)
|
||||
vc.packetsToFilter = 0
|
||||
return
|
||||
}
|
||||
log.Debugln("XTLS Vision found inconclusive server hello, packetLength=%d, remainingServerHelloBytes=%d", lenP, vc.remainingServerHello)
|
||||
}
|
||||
if vc.packetsToFilter <= 0 {
|
||||
log.Debugln("XTLS Vision stop filtering")
|
||||
}
|
||||
return
|
||||
}
|
81
transport/vless/vision.go
Normal file
81
transport/vless/vision.go
Normal file
@ -0,0 +1,81 @@
|
||||
package vless
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/Dreamacro/clash/common/buf"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
const (
|
||||
paddingHeaderLen = 1 + 2 + 2 // =5
|
||||
|
||||
commandPaddingContinue byte = 0x00
|
||||
commandPaddingEnd byte = 0x01
|
||||
commandPaddingDirect byte = 0x02
|
||||
)
|
||||
|
||||
func WriteWithPadding(buffer *buf.Buffer, p []byte, command byte, userUUID *uuid.UUID, paddingTLS bool) {
|
||||
contentLen := int32(len(p))
|
||||
var paddingLen int32
|
||||
if contentLen < 900 {
|
||||
if paddingTLS {
|
||||
//log.Debugln("long padding")
|
||||
paddingLen = fastrand.Int31n(500) + 900 - contentLen
|
||||
} else {
|
||||
paddingLen = fastrand.Int31n(256)
|
||||
}
|
||||
}
|
||||
if userUUID != nil {
|
||||
buffer.Write(userUUID.Bytes())
|
||||
}
|
||||
|
||||
buffer.WriteByte(command)
|
||||
binary.BigEndian.PutUint16(buffer.Extend(2), uint16(contentLen))
|
||||
binary.BigEndian.PutUint16(buffer.Extend(2), uint16(paddingLen))
|
||||
buffer.Write(p)
|
||||
|
||||
buffer.Extend(int(paddingLen))
|
||||
log.Debugln("XTLS Vision write padding1: command=%v, payloadLen=%v, paddingLen=%v", command, contentLen, paddingLen)
|
||||
}
|
||||
|
||||
func ApplyPadding(buffer *buf.Buffer, command byte, userUUID *uuid.UUID, paddingTLS bool) {
|
||||
contentLen := int32(buffer.Len())
|
||||
var paddingLen int32
|
||||
if contentLen < 900 {
|
||||
if paddingTLS {
|
||||
//log.Debugln("long padding")
|
||||
paddingLen = fastrand.Int31n(500) + 900 - contentLen
|
||||
} else {
|
||||
paddingLen = fastrand.Int31n(256)
|
||||
}
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(paddingLen))
|
||||
binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(contentLen))
|
||||
buffer.ExtendHeader(1)[0] = command
|
||||
if userUUID != nil {
|
||||
copy(buffer.ExtendHeader(uuid.Size), userUUID.Bytes())
|
||||
}
|
||||
|
||||
buffer.Extend(int(paddingLen))
|
||||
log.Debugln("XTLS Vision write padding2: command=%d, payloadLen=%d, paddingLen=%d", command, contentLen, paddingLen)
|
||||
}
|
||||
|
||||
func ReshapeBuffer(buffer *buf.Buffer) *buf.Buffer {
|
||||
if buffer.Len() <= buf.BufferSize-paddingHeaderLen {
|
||||
return nil
|
||||
}
|
||||
cutAt := bytes.LastIndex(buffer.Bytes(), tlsApplicationDataStart)
|
||||
if cutAt == -1 {
|
||||
cutAt = buf.BufferSize / 2
|
||||
}
|
||||
buffer2 := buf.New()
|
||||
buffer2.Write(buffer.From(cutAt))
|
||||
buffer.Truncate(cutAt)
|
||||
return buffer2
|
||||
}
|
@ -12,6 +12,7 @@ const (
|
||||
XRO = "xtls-rprx-origin"
|
||||
XRD = "xtls-rprx-direct"
|
||||
XRS = "xtls-rprx-splice"
|
||||
XRV = "xtls-rprx-vision"
|
||||
|
||||
Version byte = 0 // protocol version. preview version is 0
|
||||
)
|
||||
|
@ -2,6 +2,7 @@ package vless
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
@ -9,6 +10,10 @@ import (
|
||||
xtls "github.com/xtls/go"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotTLS13 = errors.New("XTLS Vision based on TLS 1.3 outer connection")
|
||||
)
|
||||
|
||||
type XTLSConfig struct {
|
||||
Host string
|
||||
SkipCertVerify bool
|
||||
|
@ -11,17 +11,13 @@ import (
|
||||
"errors"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// Conn wrapper a net.Conn with vmess protocol
|
||||
type Conn struct {
|
||||
net.Conn
|
||||
@ -76,7 +72,7 @@ func (vc *Conn) sendRequest() error {
|
||||
buf.WriteByte(vc.respV)
|
||||
buf.WriteByte(OptionChunkStream)
|
||||
|
||||
p := rand.Intn(16)
|
||||
p := fastrand.Intn(16)
|
||||
// P Sec Reserve Cmd
|
||||
buf.WriteByte(byte(p<<4) | byte(vc.security))
|
||||
buf.WriteByte(0)
|
||||
@ -94,7 +90,7 @@ func (vc *Conn) sendRequest() error {
|
||||
// padding
|
||||
if p > 0 {
|
||||
padding := make([]byte, p)
|
||||
rand.Read(padding)
|
||||
fastrand.Read(padding)
|
||||
buf.Write(padding)
|
||||
}
|
||||
|
||||
@ -200,7 +196,7 @@ func hashTimestamp(t time.Time) []byte {
|
||||
// newConn return a Conn instance
|
||||
func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security, isAead bool) (*Conn, error) {
|
||||
randBytes := make([]byte, 33)
|
||||
rand.Read(randBytes)
|
||||
fastrand.Read(randBytes)
|
||||
reqBodyIV := make([]byte, 16)
|
||||
reqBodyKey := make([]byte, 16)
|
||||
copy(reqBodyIV[:], randBytes[:16])
|
||||
|
@ -2,11 +2,11 @@ package vmess
|
||||
|
||||
import (
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
@ -26,7 +26,7 @@ type H2Config struct {
|
||||
func (hc *h2Conn) establishConn() error {
|
||||
preader, pwriter := io.Pipe()
|
||||
|
||||
host := hc.cfg.Hosts[rand.Intn(len(hc.cfg.Hosts))]
|
||||
host := hc.cfg.Hosts[fastrand.Intn(len(hc.cfg.Hosts))]
|
||||
path := hc.cfg.Path
|
||||
// TODO: connect use VMess Host instead of H2 Host
|
||||
req := http.Request{
|
||||
|
@ -4,10 +4,11 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
type httpConn struct {
|
||||
@ -51,16 +52,16 @@ func (hc *httpConn) Write(b []byte) (int, error) {
|
||||
return hc.Conn.Write(b)
|
||||
}
|
||||
|
||||
path := hc.cfg.Path[rand.Intn(len(hc.cfg.Path))]
|
||||
path := hc.cfg.Path[fastrand.Intn(len(hc.cfg.Path))]
|
||||
host := hc.cfg.Host
|
||||
if header := hc.cfg.Headers["Host"]; len(header) != 0 {
|
||||
host = header[rand.Intn(len(header))]
|
||||
host = header[fastrand.Intn(len(header))]
|
||||
}
|
||||
|
||||
u := fmt.Sprintf("http://%s%s", host, path)
|
||||
req, _ := http.NewRequest("GET", u, bytes.NewBuffer(b))
|
||||
for key, list := range hc.cfg.Headers {
|
||||
req.Header.Set(key, list[rand.Intn(len(list))])
|
||||
req.Header.Set(key, list[fastrand.Intn(len(list))])
|
||||
}
|
||||
req.ContentLength = int64(len(b))
|
||||
if err := req.Write(hc.Conn); err != nil {
|
||||
|
@ -3,6 +3,7 @@ package vmess
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
@ -15,6 +16,7 @@ type TLSConfig struct {
|
||||
FingerPrint string
|
||||
ClientFingerprint string
|
||||
NextProtos []string
|
||||
Reality *tlsC.RealityConfig
|
||||
}
|
||||
|
||||
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 {
|
||||
utlsConn, valid := GetUtlsConnWithClientFingerprint(conn, cfg.ClientFingerprint, tlsConfig)
|
||||
if valid {
|
||||
if cfg.Reality == nil {
|
||||
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)
|
||||
defer cancel()
|
||||
|
||||
err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx)
|
||||
return utlsConn, err
|
||||
return tlsC.GetRealityConn(ctx, conn, cfg.ClientFingerprint, tlsConfig, cfg.Reality)
|
||||
}
|
||||
}
|
||||
if cfg.Reality != nil {
|
||||
return nil, errors.New("REALITY is based on uTLS, please set a client-fingerprint")
|
||||
}
|
||||
|
||||
tlsConn := tls.Client(conn, tlsConfig)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
utlsConn := tlsC.UClient(conn, tlsConfig, fingerprint)
|
||||
|
@ -2,12 +2,13 @@ package vmess
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"math/rand"
|
||||
"net"
|
||||
"runtime"
|
||||
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
// Version of vmess
|
||||
@ -77,7 +78,7 @@ type Config struct {
|
||||
|
||||
// StreamConn return a Conn with net.Conn and DstAddr
|
||||
func (c *Client) StreamConn(conn net.Conn, dst *DstAddr) (net.Conn, error) {
|
||||
r := rand.Intn(len(c.user))
|
||||
r := fastrand.Intn(len(c.user))
|
||||
return newConn(conn, c.user[r], dst, c.security, c.isAead)
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user